Skip to content

Commit

Permalink
Implement web getStoreUtils
Browse files Browse the repository at this point in the history
  • Loading branch information
richardguerre committed May 27, 2023
1 parent d5c38a8 commit acf7461
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 45 deletions.
53 changes: 38 additions & 15 deletions apps/server/src/graphql/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,29 @@ export const SettingType = builder.prismaNode("Store", {

// --------------- Setting query types ---------------

builder.queryField("store", (t) =>
t.prismaConnection({
type: "Store",
cursor: "id",
description: "Get all store items. The args (first, last, after, before) don't work yet.",
// TODO: add pagination. See https://github.com/devoxa/prisma-relay-cursor-connection/blob/936a800b8ec4cf62b644bce4c0c0fdf7a90f5e7c/src/index.ts#L12 or https://gist.github.com/ctrlplusb/17b5a1bd1736b5ba547bb15b3dd5be29#file-findmanycursor-ts
resolve: (query) => {
builder.queryField("storeItems", (t) =>
// TODO: maybe change to prismaConnection and add pagination. See https://github.com/devoxa/prisma-relay-cursor-connection/blob/936a800b8ec4cf62b644bce4c0c0fdf7a90f5e7c/src/index.ts#L12 or https://gist.github.com/ctrlplusb/17b5a1bd1736b5ba547bb15b3dd5be29#file-findmanycursor-ts
t.prismaFieldWithInput({
type: ["Store"],
description: `Get store items.
Pass the \`pluginSlug\` if you want to get items created by a specific plugin. Not passing the \`pluginSlug\` will return items created by flow.`,
input: {
pluginSlug: t.input.string({ required: false }),
keys: t.input.stringList({ required: false }),
},
argOptions: {
required: false,
},
resolve: (query, _, args) => {
return prisma.store.findMany({
...query,
where: { isSecret: false, isServerOnly: false },
where: {
isSecret: false,
isServerOnly: false,
pluginSlug: args.input?.pluginSlug ?? null,
...(args.input?.keys?.length ? { key: { in: args.input.keys } } : {}),
},
});
},
})
Expand Down Expand Up @@ -75,24 +88,34 @@ const PluginInstallationType = builder.objectType(

// --------------- Setting mutation types ---------------

builder.mutationField("createStoreItem", (t) =>
builder.mutationField("upsertStoreItem", (t) =>
t.prismaFieldWithInput({
type: "Store",
description: "Create a store item.",
description:
"Creates a store item. If a store item with the same key exists, it will be updated.",
input: {
key: t.input.string({ required: true }),
value: t.input.field({ type: "JSON", required: true }),
pluginSlug: t.input.string({ required: true }),
pluginSlug: t.input.string({ required: false }),
isSecret: t.input.boolean({ required: false }),
isServerOnly: t.input.boolean({ required: false }),
},
resolve: (query, _, args) => {
return prisma.store.create({
const pluginSlug = args.input.pluginSlug;
return prisma.store.upsert({
...query,
data: {
where: {
...(pluginSlug
? { pluginSlug_key_unique: { key: args.input.key, pluginSlug } }
: { key: args.input.key }),
},
update: { value: args.input.value },
create: {
key: args.input.key,
value: args.input.value,
pluginSlug: args.input.pluginSlug,
isSecret: false,
isServerOnly: false,
isSecret: args.input.isSecret ?? false,
isServerOnly: args.input.isServerOnly ?? false,
},
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import { ItemCard } from "../components/ItemCard";
import { useAsyncLoader } from "../useAsyncLoader";
import { createItem } from "./createItem";
import { createTask } from "./createTask";
import { getStoreUtils } from "./getStoreUtils";

export const pluginOptions = {
export const getPluginOptions = (slug: string) => ({
/**
* Components that Flow uses you can re-use in your plugin views to feel more integrated.
*
Expand Down Expand Up @@ -50,9 +51,7 @@ export const pluginOptions = {
*/
useAsyncLoader,
},
store: {
// TODO: add `get` and `useStore` functions here
},
store: getStoreUtils(slug),
/**
* Get days between 2 dates (inclusive) by passing `from` and `to` as part of the options.
* If you want to get specific/discreate days, use `getDaysMax10` instead.
Expand Down Expand Up @@ -104,6 +103,6 @@ export const pluginOptions = {
* The dayjs package. This prevents double bundling it in both the web app and in individual plugins.
*/
dayjs,
};
});

export type WebPluginOptions = typeof pluginOptions;
export type WebPluginOptions = ReturnType<typeof getPluginOptions>;
37 changes: 37 additions & 0 deletions apps/web/src/getPlugin/getStoreUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { fetchQuery, graphql } from "@flowdev/relay";
import { useAsyncLoader } from "../useAsyncLoader";
import { environment } from "../relay/environment";
import { getStoreUtilsGetQuery } from "../relay/__generated__/getStoreUtilsGetQuery.graphql";

export const getStoreUtils = (defaultSlug: string) => {
/** The pluginSlug is optional. You can pass `null` if you want to get an item from flow (e.g. theme). */
const get = async (keys?: string[], pluginSlug?: string | null) => {
const data = await fetchQuery<getStoreUtilsGetQuery>(
environment,
graphql`
query getStoreUtilsGetQuery($input: QueryStoreItemsInput!) {
storeItems(input: $input) {
key
value
}
}
`,
{
input: {
keys,
pluginSlug: pluginSlug === undefined ? defaultSlug : pluginSlug,
},
}
).toPromise();
return Object.fromEntries(data?.storeItems.map((item) => [item.key, item.value]) ?? []);
};

return {
get,
useStore: (keys?: string[], pluginSlug?: string) => {
return useAsyncLoader(async () => {
return get(keys, pluginSlug);
});
},
};
};
6 changes: 3 additions & 3 deletions apps/web/src/getPlugin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { graphql } from "@flowdev/relay";
import { fetchQuery } from "@flowdev/relay";
import { getPluginsQuery } from "@flowdev/web/relay/__generated__/getPluginsQuery.graphql";
import { environment } from "@flowdev/web/relay/environment";
import { pluginOptions } from "./pluginOptions";
import { getPluginOptions } from "./getPluginOptions";

type Input = {
pluginSlug: string;
Expand All @@ -28,8 +28,8 @@ export const getPlugin = async (input: Input) => {
: import(/* @vite-ignore */ `${pluginInstallation.url}/web.js`);

// TODO: use plugin's slug if needed
const { plugin } = (await importPromise).default as DefineWebPluginReturn;
return plugin(pluginOptions);
const { plugin, slug } = (await importPromise).default as DefineWebPluginReturn;
return plugin(getPluginOptions(slug));
} catch (e) {
console.log(e);
return {
Expand Down
39 changes: 20 additions & 19 deletions apps/web/src/relay/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,6 @@ type Mutation {
"""
createOrUpdateNote(input: MutationCreateOrUpdateNoteInput!): Note!

"""Create a store item."""
createStoreItem(input: MutationCreateStoreItemInput!): Store!

"""Create a new task."""
createTask(input: MutationCreateTaskInput!): Task!

Expand Down Expand Up @@ -198,6 +195,11 @@ type Mutation {

"Update the status of a task and get the updated days (as a list in chronological order).\n\nWhen the task is:\n- already in the desired status, it does nothing and returns an empty list.\n- for today, it updates the status and returns the day.\n- for a previous day and changing to `TODO`, it updates the status and\n returns the original day and today.\n- for a future day and changing to `DONE` or `CANCELED`, it updates the status and\n returns the original day and today.\n\nAny other scenario is not possible by nature of the app, where tasks:\n- in the past can only be `DONE` or `CANCELED` \n- in the future can only be in `TODO`\n "
updateTaskStatus(input: MutationUpdateTaskStatusInput!): [Day!]!

"""
Creates a store item. If a store item with the same key exists, it will be updated.
"""
upsertStoreItem(input: MutationUpsertStoreItemInput!): Store!
}

input MutationCompleteRoutineInput {
Expand Down Expand Up @@ -252,12 +254,6 @@ input MutationCreateOrUpdateNoteInput {
title: String!
}

input MutationCreateStoreItemInput {
key: String!
pluginSlug: String!
value: JSON!
}

input MutationCreateTaskInput {
"""The day (no time required) the task is planned for."""
date: Date
Expand Down Expand Up @@ -315,6 +311,14 @@ input MutationUpdateTaskStatusInput {
superDone: Boolean
}

input MutationUpsertStoreItemInput {
isSecret: Boolean
isServerOnly: Boolean
key: String!
pluginSlug: String
value: JSON!
}

interface Node {
id: ID!
}
Expand Down Expand Up @@ -456,9 +460,11 @@ type Query {
routines: [Routine!]!

"""
Get all store items. The args (first, last, after, before) don't work yet.
Get store items.
Pass the `pluginSlug` if you want to get items created by a specific plugin. Not passing the `pluginSlug` will return items created by flow.
"""
store(after: ID, before: ID, first: Int, last: Int): QueryStoreConnection!
storeItems(input: QueryStoreItemsInput): [Store!]!

"""
Get all task labels ordered by usage in descending order. `before` and `after` cursors are ignored, and `first` and `last` act the same and are limited to 100.
Expand Down Expand Up @@ -504,14 +510,9 @@ type QueryNoteLabelsConnectionEdge {
node: NoteLabel!
}

type QueryStoreConnection {
edges: [QueryStoreConnectionEdge!]!
pageInfo: PageInfo!
}

type QueryStoreConnectionEdge {
cursor: ID!
node: Store!
input QueryStoreItemsInput {
keys: [String!]
pluginSlug: String
}

type QueryTaskLabelsConnection {
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ interface ImportMetaEnv {
interface ImportMeta {
readonly env: ImportMetaEnv;
}

type JsonValue = string | number | boolean | { [Key in string]?: JsonValue } | Array<JsonValue>;
3 changes: 2 additions & 1 deletion packages/plugin/web/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ‼️ only import **types** from the @flowdev/web package, not runtime code otherwise it will be a cyclic dependency
import type { WebPluginOptions } from "@flowdev/web/src/getPlugin/pluginOptions";
import type { WebPluginOptions } from "@flowdev/web/src/getPlugin/getPluginOptions";
import type { PluginRoutineStepProps } from "@flowdev/web/src/components/RoutineStep";

export type { WebPluginOptions, PluginRoutineStepProps };
Expand Down Expand Up @@ -37,6 +37,7 @@ export const definePlugin = (slug: string, plugin: WebPlugin) => ({ slug, plugin

export type DefineWebPluginReturn = ReturnType<typeof definePlugin>;

// copied from prisma types
type JsonValue = string | number | boolean | { [Key in string]?: JsonValue } | Array<JsonValue>;

type SettingField =
Expand Down
2 changes: 1 addition & 1 deletion relay.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"Date": "string",
"DateTime": "string",
"PositiveInt": "number",
"JSON": "object",
"JSON": "JsonValue",
"Time": "string"
},
"exclude": ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"]
Expand Down

0 comments on commit acf7461

Please sign in to comment.