@zenstackhq/client-helpers 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/logging.ts","../src/nested-read-visitor.ts","../src/nested-write-visitor.ts","../src/types.ts","../src/query-analysis.ts","../src/invalidation.ts","../src/mutator.ts","../src/optimistic.ts"],"sourcesContent":["/**\n * The default query endpoint.\n */\nexport const DEFAULT_QUERY_ENDPOINT = '/api/model';\n","/**\n * Logger configuration. `true` enables console logging. A function can be provided for custom logging.\n */\nexport type Logger = boolean | ((message: string) => void);\n\n/**\n * Logs a message using the provided logger.\n */\nexport function log(logger: Logger, message: string) {\n if (typeof logger === 'function') {\n logger(message);\n } else if (logger) {\n console.log(message);\n }\n}\n","import type { FieldDef, SchemaDef } from '@zenstackhq/schema';\n\n/**\n * Callback functions for nested read visitor.\n */\nexport type NestedReadVisitorCallback = {\n /**\n * Callback for each field visited.\n * @returns If returns false, traversal will not continue into this field.\n */\n field?: (\n model: string,\n field: FieldDef | undefined,\n kind: 'include' | 'select' | undefined,\n args: unknown,\n ) => void | boolean;\n};\n\n/**\n * Visitor for nested read payload.\n */\nexport class NestedReadVisitor {\n constructor(\n private readonly schema: SchemaDef,\n private readonly callback: NestedReadVisitorCallback,\n ) {}\n\n private doVisit(model: string, field: FieldDef | undefined, kind: 'include' | 'select' | undefined, args: unknown) {\n if (this.callback.field) {\n const r = this.callback.field(model, field, kind, args);\n if (r === false) {\n return;\n }\n }\n\n if (!args || typeof args !== 'object') {\n return;\n }\n\n let selectInclude: any;\n let nextKind: 'select' | 'include' | undefined;\n if ((args as any).select) {\n selectInclude = (args as any).select;\n nextKind = 'select';\n } else if ((args as any).include) {\n selectInclude = (args as any).include;\n nextKind = 'include';\n }\n\n if (selectInclude && typeof selectInclude === 'object') {\n for (const [k, v] of Object.entries(selectInclude)) {\n if (k === '_count' && typeof v === 'object' && v) {\n // recurse into { _count: { ... } }\n this.doVisit(model, field, kind, v);\n } else {\n const field = this.schema.models[model]?.fields[k];\n if (field) {\n this.doVisit(field.type, field, nextKind, v);\n }\n }\n }\n }\n }\n\n visit(model: string, args: unknown) {\n this.doVisit(model, undefined, undefined, args);\n }\n}\n","import { enumerate } from '@zenstackhq/common-helpers';\nimport type { FieldDef, SchemaDef } from '@zenstackhq/schema';\nimport { ORMWriteActions, type MaybePromise, type ORMWriteActionType } from './types';\n\ntype NestingPathItem = { field?: FieldDef; model: string; where: any; unique: boolean };\n\n/**\n * Context for visiting\n */\nexport type NestedWriteVisitorContext = {\n /**\n * Parent data, can be used to replace fields\n */\n parent: any;\n\n /**\n * Current field, undefined if toplevel\n */\n field?: FieldDef;\n\n /**\n * A top-down path of all nested update conditions and corresponding field till now\n */\n nestingPath: NestingPathItem[];\n};\n\n/**\n * NestedWriteVisitor's callback actions. A call back function should return true or void to indicate\n * that the visitor should continue traversing its children, or false to stop. It can also return an object\n * to let the visitor traverse it instead of its original children.\n */\nexport type NestedWriteVisitorCallback = {\n create?: (model: string, data: any, context: NestedWriteVisitorContext) => MaybePromise<boolean | object | void>;\n\n createMany?: (\n model: string,\n args: { data: any; skipDuplicates?: boolean },\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n connectOrCreate?: (\n model: string,\n args: { where: object; create: any },\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n connect?: (\n model: string,\n args: object,\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n disconnect?: (\n model: string,\n args: object,\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n set?: (model: string, args: object, context: NestedWriteVisitorContext) => MaybePromise<boolean | object | void>;\n\n update?: (model: string, args: object, context: NestedWriteVisitorContext) => MaybePromise<boolean | object | void>;\n\n updateMany?: (\n model: string,\n args: { where?: object; data: any },\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n upsert?: (\n model: string,\n args: { where: object; create: any; update: any },\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n delete?: (\n model: string,\n args: object | boolean,\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n deleteMany?: (\n model: string,\n args: any | object,\n context: NestedWriteVisitorContext,\n ) => MaybePromise<boolean | object | void>;\n\n field?: (\n field: FieldDef,\n action: ORMWriteActionType,\n data: any,\n context: NestedWriteVisitorContext,\n ) => MaybePromise<void>;\n};\n\n/**\n * Recursive visitor for nested write (create/update) payload.\n */\nexport class NestedWriteVisitor {\n constructor(\n private readonly schema: SchemaDef,\n private readonly callback: NestedWriteVisitorCallback,\n ) {}\n\n private isWriteAction(value: string): value is ORMWriteActionType {\n return ORMWriteActions.includes(value as ORMWriteActionType);\n }\n\n /**\n * Start visiting\n *\n * @see NestedWriteVisitorCallback\n */\n async visit(model: string, action: ORMWriteActionType, args: any): Promise<void> {\n if (!args) {\n return;\n }\n\n let topData = args;\n\n switch (action) {\n // create has its data wrapped in 'data' field\n case 'create':\n topData = topData.data;\n break;\n\n case 'delete':\n case 'deleteMany':\n topData = topData.where;\n break;\n }\n\n await this.doVisit(model, action, topData, undefined, undefined, []);\n }\n\n private async doVisit(\n model: string,\n action: ORMWriteActionType,\n data: any,\n parent: any,\n field: FieldDef | undefined,\n nestingPath: NestingPathItem[],\n ): Promise<void> {\n if (!data) {\n return;\n }\n\n const toplevel = field == undefined;\n\n const context = { parent, field, nestingPath: [...nestingPath] };\n const pushNewContext = (field: FieldDef | undefined, model: string, where: any, unique = false) => {\n return { ...context, nestingPath: [...context.nestingPath, { field, model, where, unique }] };\n };\n\n // visit payload\n switch (action) {\n case 'create':\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, {});\n let callbackResult: any;\n if (this.callback.create) {\n callbackResult = await this.callback.create(model, item, newContext);\n }\n if (callbackResult !== false) {\n const subPayload = typeof callbackResult === 'object' ? callbackResult : item;\n await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);\n }\n }\n break;\n\n case 'createMany':\n case 'createManyAndReturn':\n {\n const newContext = pushNewContext(field, model, {});\n let callbackResult: any;\n if (this.callback.createMany) {\n callbackResult = await this.callback.createMany(model, data, newContext);\n }\n if (callbackResult !== false) {\n const subPayload = typeof callbackResult === 'object' ? callbackResult : data.data;\n await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);\n }\n }\n break;\n\n case 'connectOrCreate':\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item.where);\n let callbackResult: any;\n if (this.callback.connectOrCreate) {\n callbackResult = await this.callback.connectOrCreate(model, item, newContext);\n }\n if (callbackResult !== false) {\n const subPayload = typeof callbackResult === 'object' ? callbackResult : item.create;\n await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);\n }\n }\n break;\n\n case 'connect':\n if (this.callback.connect) {\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item, true);\n await this.callback.connect(model, item, newContext);\n }\n }\n break;\n\n case 'disconnect':\n // disconnect has two forms:\n // if relation is to-many, the payload is a unique filter object\n // if relation is to-one, the payload can only be boolean `true`\n if (this.callback.disconnect) {\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item, typeof item === 'object');\n await this.callback.disconnect(model, item, newContext);\n }\n }\n break;\n\n case 'set':\n if (this.callback.set) {\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item, true);\n await this.callback.set(model, item, newContext);\n }\n }\n break;\n\n case 'update':\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item.where);\n let callbackResult: any;\n if (this.callback.update) {\n callbackResult = await this.callback.update(model, item, newContext);\n }\n if (callbackResult !== false) {\n const subPayload =\n typeof callbackResult === 'object'\n ? callbackResult\n : typeof item.data === 'object'\n ? item.data\n : item;\n await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);\n }\n }\n break;\n\n case 'updateMany':\n case 'updateManyAndReturn':\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item.where);\n let callbackResult: any;\n if (this.callback.updateMany) {\n callbackResult = await this.callback.updateMany(model, item, newContext);\n }\n if (callbackResult !== false) {\n const subPayload = typeof callbackResult === 'object' ? callbackResult : item;\n await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);\n }\n }\n break;\n\n case 'upsert': {\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, item.where);\n let callbackResult: any;\n if (this.callback.upsert) {\n callbackResult = await this.callback.upsert(model, item, newContext);\n }\n if (callbackResult !== false) {\n if (typeof callbackResult === 'object') {\n await this.visitSubPayload(model, action, callbackResult, newContext.nestingPath);\n } else {\n await this.visitSubPayload(model, action, item.create, newContext.nestingPath);\n await this.visitSubPayload(model, action, item.update, newContext.nestingPath);\n }\n }\n }\n break;\n }\n\n case 'delete': {\n if (this.callback.delete) {\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, toplevel ? item.where : item);\n await this.callback.delete(model, item, newContext);\n }\n }\n break;\n }\n\n case 'deleteMany':\n if (this.callback.deleteMany) {\n for (const item of this.enumerateReverse(data)) {\n const newContext = pushNewContext(field, model, toplevel ? item.where : item);\n await this.callback.deleteMany(model, item, newContext);\n }\n }\n break;\n\n default: {\n throw new Error(`unhandled action type ${action}`);\n }\n }\n }\n\n private async visitSubPayload(\n model: string,\n action: ORMWriteActionType,\n payload: any,\n nestingPath: NestingPathItem[],\n ) {\n for (const item of enumerate(payload)) {\n if (!item || typeof item !== 'object') {\n continue;\n }\n for (const field of Object.keys(item)) {\n const fieldDef = this.schema.models[model]?.fields[field];\n if (!fieldDef) {\n continue;\n }\n\n if (fieldDef.relation) {\n if (item[field]) {\n // recurse into nested payloads\n for (const [subAction, subData] of Object.entries<any>(item[field])) {\n if (this.isWriteAction(subAction) && subData) {\n await this.doVisit(fieldDef.type, subAction, subData, item[field], fieldDef, [\n ...nestingPath,\n ]);\n }\n }\n }\n } else {\n // visit plain field\n if (this.callback.field) {\n await this.callback.field(fieldDef, action, item[field], {\n parent: item,\n nestingPath,\n field: fieldDef,\n });\n }\n }\n }\n }\n }\n\n // enumerate a (possible) array in reverse order, so that the enumeration\n // callback can safely delete the current item\n private *enumerateReverse(data: any) {\n if (Array.isArray(data)) {\n for (let i = data.length - 1; i >= 0; i--) {\n yield data[i];\n }\n } else {\n yield data;\n }\n }\n}\n","/**\n * A type that represents either a value of type T or a Promise that resolves to type T.\n */\nexport type MaybePromise<T> = T | Promise<T> | PromiseLike<T>;\n\n/**\n * List of ORM write actions.\n */\nexport const ORMWriteActions = [\n 'create',\n 'createMany',\n 'createManyAndReturn',\n 'connectOrCreate',\n 'update',\n 'updateMany',\n 'updateManyAndReturn',\n 'upsert',\n 'connect',\n 'disconnect',\n 'set',\n 'delete',\n 'deleteMany',\n] as const;\n\n/**\n * Type representing ORM write action types.\n */\nexport type ORMWriteActionType = (typeof ORMWriteActions)[number];\n\n/**\n * Type for query and mutation errors.\n */\nexport type QueryError = Error & {\n /**\n * Additional error information.\n */\n info?: unknown;\n\n /**\n * HTTP status code.\n */\n status?: number;\n};\n\n/**\n * Information about a cached query.\n */\nexport type QueryInfo = {\n /**\n * Model of the query.\n */\n model: string;\n\n /**\n * Query operation, e.g., `findUnique`\n */\n operation: string;\n\n /**\n * Query arguments.\n */\n args: unknown;\n\n /**\n * Current data cached for this query.\n */\n data: unknown;\n\n /**\n * Whether optimistic update is enabled for this query.\n */\n optimisticUpdate: boolean;\n\n /**\n * Function to update the cached data.\n *\n * @param data New data to set.\n * @param cancelOnTheFlyQueries Whether to cancel on-the-fly queries to avoid accidentally\n * overwriting the optimistic update.\n */\n updateData: (data: unknown, cancelOnTheFlyQueries: boolean) => void;\n};\n","import type { SchemaDef } from '@zenstackhq/schema';\nimport { NestedReadVisitor } from './nested-read-visitor';\nimport { NestedWriteVisitor } from './nested-write-visitor';\nimport type { ORMWriteActionType } from './types';\n\n/**\n * Gets models read (including nested ones) given a query args.\n */\nexport function getReadModels(model: string, schema: SchemaDef, args: any) {\n const result = new Set<string>();\n result.add(model);\n const visitor = new NestedReadVisitor(schema, {\n field: (model) => {\n result.add(model);\n return true;\n },\n });\n visitor.visit(model, args);\n return [...result];\n}\n\n/**\n * Gets mutated models (including nested ones) given a mutation args.\n */\nexport async function getMutatedModels(\n model: string,\n operation: ORMWriteActionType,\n mutationArgs: any,\n schema: SchemaDef,\n) {\n const result = new Set<string>();\n result.add(model);\n\n if (mutationArgs) {\n const addModel = (model: string) => void result.add(model);\n\n // add models that are cascaded deleted recursively\n const addCascades = (model: string) => {\n const cascades = new Set<string>();\n const visited = new Set<string>();\n collectDeleteCascades(model, schema, cascades, visited);\n cascades.forEach((m) => addModel(m));\n };\n\n const visitor = new NestedWriteVisitor(schema, {\n create: addModel,\n createMany: addModel,\n connectOrCreate: addModel,\n connect: addModel,\n disconnect: addModel,\n set: addModel,\n update: addModel,\n updateMany: addModel,\n upsert: addModel,\n delete: (model) => {\n addModel(model);\n addCascades(model);\n },\n deleteMany: (model) => {\n addModel(model);\n addCascades(model);\n },\n });\n await visitor.visit(model, operation, mutationArgs);\n }\n\n // include delegate base models recursively\n result.forEach((m) => {\n getBaseRecursively(m, schema, result);\n });\n\n return [...result];\n}\n\nfunction collectDeleteCascades(model: string, schema: SchemaDef, result: Set<string>, visited: Set<string>) {\n if (visited.has(model)) {\n // break circle\n return;\n }\n visited.add(model);\n\n const modelDef = schema.models[model];\n if (!modelDef) {\n return;\n }\n\n for (const [modelName, modelDef] of Object.entries(schema.models)) {\n if (!modelDef) {\n continue;\n }\n for (const fieldDef of Object.values(modelDef.fields)) {\n if (fieldDef.relation?.onDelete === 'Cascade' && fieldDef.type === model) {\n if (!result.has(modelName)) {\n result.add(modelName);\n }\n collectDeleteCascades(modelName, schema, result, visited);\n }\n }\n }\n}\n\nfunction getBaseRecursively(model: string, schema: SchemaDef, result: Set<string>) {\n const modelDef = schema.models[model];\n if (!modelDef) {\n return;\n }\n if (modelDef.baseModel) {\n result.add(modelDef.baseModel);\n getBaseRecursively(modelDef.baseModel, schema, result);\n }\n}\n","import type { SchemaDef } from '@zenstackhq/schema';\nimport { log, type Logger } from './logging';\nimport { getMutatedModels, getReadModels } from './query-analysis';\nimport type { MaybePromise, ORMWriteActionType } from './types';\n\n/**\n * Type for a predicate that determines whether a query should be invalidated.\n */\nexport type InvalidationPredicate = ({ model, args }: { model: string; args: unknown }) => boolean;\n\n/**\n * Type for a function that invalidates queries matching the given predicate.\n */\nexport type InvalidateFunc = (predicate: InvalidationPredicate) => MaybePromise<void>;\n\n/**\n * Create a function that invalidates queries affected by the given mutation operation.\n *\n * @param model Model under mutation.\n * @param operation Mutation operation (e.g, `update`).\n * @param schema The schema.\n * @param invalidator Function to invalidate queries matching a predicate. It should internally\n * enumerate all query cache entries and invalidate those for which the predicate returns true.\n * @param logging Logging option.\n */\nexport function createInvalidator(\n model: string,\n operation: string,\n schema: SchemaDef,\n invalidator: InvalidateFunc,\n logging: Logger | undefined,\n) {\n return async (...args: unknown[]) => {\n const [_, variables] = args;\n const predicate = await getInvalidationPredicate(\n model,\n operation as ORMWriteActionType,\n variables,\n schema,\n logging,\n );\n await invalidator(predicate);\n };\n}\n\n// gets a predicate for evaluating whether a query should be invalidated\nasync function getInvalidationPredicate(\n model: string,\n operation: ORMWriteActionType,\n mutationArgs: any,\n schema: SchemaDef,\n logging: Logger | undefined,\n): Promise<InvalidationPredicate> {\n const mutatedModels = await getMutatedModels(model, operation, mutationArgs, schema);\n\n return ({ model, args }) => {\n if (mutatedModels.includes(model)) {\n // direct match\n if (logging) {\n log(\n logging,\n `Marking \"${model}\" query for invalidation due to mutation \"${operation}\", query args: ${JSON.stringify(args)}`,\n );\n }\n return true;\n }\n\n if (args) {\n // traverse query args to find nested reads that match the model under mutation\n if (findNestedRead(model, mutatedModels, schema, args)) {\n if (logging) {\n log(\n logging,\n `Marking \"${model}\" query for invalidation due to mutation \"${operation}\", query args: ${JSON.stringify(args)}`,\n );\n }\n return true;\n }\n }\n\n return false;\n };\n}\n\n// find nested reads that match the given models\nfunction findNestedRead(visitingModel: string, targetModels: string[], schema: SchemaDef, args: any) {\n const modelsRead = getReadModels(visitingModel, schema, args);\n return targetModels.some((m) => modelsRead.includes(m));\n}\n","import { clone, enumerate, invariant, zip } from '@zenstackhq/common-helpers';\nimport type { FieldDef, SchemaDef } from '@zenstackhq/schema';\nimport { log, type Logger } from './logging';\nimport { NestedWriteVisitor } from './nested-write-visitor';\nimport type { ORMWriteActionType } from './types';\n\n/**\n * Tries to apply a mutation to a query result.\n *\n * @param queryModel the model of the query\n * @param queryOp the operation of the query\n * @param queryData the result data of the query\n * @param mutationModel the model of the mutation\n * @param mutationOp the operation of the mutation\n * @param mutationArgs the arguments of the mutation\n * @param schema the schema\n * @param logging logging configuration\n * @returns the updated query data if the mutation is applicable, otherwise undefined\n */\nexport async function applyMutation(\n queryModel: string,\n queryOp: string,\n queryData: any,\n mutationModel: string,\n mutationOp: ORMWriteActionType,\n mutationArgs: any,\n schema: SchemaDef,\n logging: Logger | undefined,\n) {\n if (!queryData || (typeof queryData !== 'object' && !Array.isArray(queryData))) {\n return undefined;\n }\n\n if (!queryOp.startsWith('find')) {\n // only findXXX results are applicable\n return undefined;\n }\n\n return await doApplyMutation(queryModel, queryData, mutationModel, mutationOp, mutationArgs, schema, logging);\n}\n\nasync function doApplyMutation(\n queryModel: string,\n queryData: any,\n mutationModel: string,\n mutationOp: ORMWriteActionType,\n mutationArgs: any,\n schema: SchemaDef,\n logging: Logger | undefined,\n) {\n let resultData = queryData;\n let updated = false;\n\n const visitor = new NestedWriteVisitor(schema, {\n create: (model, args) => {\n if (\n model === queryModel &&\n Array.isArray(resultData) // \"create\" mutation is only relevant for arrays\n ) {\n const r = createMutate(queryModel, resultData, args, schema, logging);\n if (r) {\n resultData = r;\n updated = true;\n }\n }\n },\n\n createMany: (model, args) => {\n if (\n model === queryModel &&\n args?.data &&\n Array.isArray(resultData) // \"createMany\" mutation is only relevant for arrays\n ) {\n for (const oneArg of enumerate(args.data)) {\n const r = createMutate(queryModel, resultData, oneArg, schema, logging);\n if (r) {\n resultData = r;\n updated = true;\n }\n }\n }\n },\n\n update: (model, args) => {\n if (\n model === queryModel &&\n !Array.isArray(resultData) // array elements will be handled with recursion\n ) {\n const r = updateMutate(queryModel, resultData, model, args, schema, logging);\n if (r) {\n resultData = r;\n updated = true;\n }\n }\n },\n\n upsert: (model, args) => {\n if (model === queryModel && args?.where && args?.create && args?.update) {\n const r = upsertMutate(queryModel, resultData, model, args, schema, logging);\n if (r) {\n resultData = r;\n updated = true;\n }\n }\n },\n\n delete: (model, args) => {\n if (model === queryModel) {\n const r = deleteMutate(queryModel, resultData, model, args, schema, logging);\n if (r) {\n resultData = r;\n updated = true;\n }\n }\n },\n });\n\n await visitor.visit(mutationModel, mutationOp, mutationArgs);\n\n const modelFields = schema.models[queryModel]?.fields;\n invariant(modelFields, `Model ${queryModel} not found in schema`);\n\n if (Array.isArray(resultData)) {\n // try to apply mutation to each item in the array, replicate the entire\n // array if any item is updated\n\n let arrayCloned = false;\n for (let i = 0; i < resultData.length; i++) {\n const item = resultData[i];\n if (\n !item ||\n typeof item !== 'object' ||\n item.$optimistic // skip items already optimistically updated\n ) {\n continue;\n }\n\n const r = await doApplyMutation(queryModel, item, mutationModel, mutationOp, mutationArgs, schema, logging);\n\n if (r && typeof r === 'object') {\n if (!arrayCloned) {\n resultData = [...resultData];\n arrayCloned = true;\n }\n resultData[i] = r;\n updated = true;\n }\n }\n } else if (resultData !== null && typeof resultData === 'object') {\n // Clone resultData to prevent mutations affecting the loop\n const currentData = { ...resultData };\n\n // iterate over each field and apply mutation to nested data models\n for (const [key, value] of Object.entries(currentData)) {\n const fieldDef = modelFields[key];\n if (!fieldDef?.relation) {\n continue;\n }\n\n const r = await doApplyMutation(\n fieldDef.type,\n value,\n mutationModel,\n mutationOp,\n mutationArgs,\n schema,\n logging,\n );\n\n if (r && typeof r === 'object') {\n resultData = { ...resultData, [key]: r };\n updated = true;\n }\n }\n }\n\n return updated ? resultData : undefined;\n}\n\nfunction createMutate(\n queryModel: string,\n currentData: any,\n newData: any,\n schema: SchemaDef,\n logging: Logger | undefined,\n) {\n if (!newData) {\n return undefined;\n }\n\n const modelFields = schema.models[queryModel]?.fields;\n if (!modelFields) {\n return undefined;\n }\n\n const insert: any = {};\n const newDataFields = Object.keys(newData);\n\n Object.entries(modelFields).forEach(([name, field]) => {\n if (field.relation && newData[name]) {\n // deal with \"connect\"\n assignForeignKeyFields(field, insert, newData[name]);\n return;\n }\n\n if (newDataFields.includes(name)) {\n insert[name] = clone(newData[name]);\n } else {\n const defaultAttr = field.attributes?.find((attr) => attr.name === '@default');\n if (field.type === 'DateTime') {\n // default value for DateTime field\n if (defaultAttr || field.attributes?.some((attr) => attr.name === '@updatedAt')) {\n insert[name] = new Date();\n return;\n }\n }\n\n const defaultArg = defaultAttr?.args?.[0]?.value;\n if (defaultArg?.kind === 'literal') {\n // other default value\n insert[name] = defaultArg.value;\n }\n }\n });\n\n // add temp id value\n const idFields = getIdFields(schema, queryModel);\n idFields.forEach((f) => {\n if (insert[f.name] === undefined) {\n if (f.type === 'Int' || f.type === 'BigInt') {\n const currMax = Array.isArray(currentData)\n ? Math.max(\n ...[...currentData].map((item) => {\n const idv = parseInt(item[f.name]);\n return isNaN(idv) ? 0 : idv;\n }),\n )\n : 0;\n insert[f.name] = currMax + 1;\n } else {\n insert[f.name] = crypto.randomUUID();\n }\n }\n });\n\n insert.$optimistic = true;\n\n if (logging) {\n log(logging, `Applying optimistic create for ${queryModel}: ${JSON.stringify(insert)}`);\n }\n\n return [insert, ...(Array.isArray(currentData) ? currentData : [])];\n}\n\nfunction updateMutate(\n queryModel: string,\n currentData: any,\n mutateModel: string,\n mutateArgs: any,\n schema: SchemaDef,\n logging: Logger | undefined,\n) {\n if (!currentData || typeof currentData !== 'object') {\n return undefined;\n }\n\n if (!mutateArgs?.where || typeof mutateArgs.where !== 'object') {\n return undefined;\n }\n\n if (!mutateArgs?.data || typeof mutateArgs.data !== 'object') {\n return undefined;\n }\n\n if (!idFieldsMatch(mutateModel, currentData, mutateArgs.where, schema)) {\n return undefined;\n }\n\n const modelFields = schema.models[queryModel]?.fields;\n if (!modelFields) {\n return undefined;\n }\n\n let updated = false;\n let resultData = currentData;\n\n for (const [key, value] of Object.entries<any>(mutateArgs.data)) {\n const fieldInfo = modelFields[key];\n if (!fieldInfo) {\n continue;\n }\n\n if (fieldInfo.relation && !value?.connect) {\n // relation field but without \"connect\"\n continue;\n }\n\n if (!updated) {\n // clone\n resultData = { ...currentData };\n }\n\n if (fieldInfo.relation) {\n // deal with \"connect\"\n assignForeignKeyFields(fieldInfo, resultData, value);\n } else {\n resultData[key] = clone(value);\n }\n resultData.$optimistic = true;\n updated = true;\n\n if (logging) {\n log(logging, `Applying optimistic update for ${queryModel}: ${JSON.stringify(resultData)}`);\n }\n }\n\n return updated ? resultData : undefined;\n}\n\nfunction upsertMutate(\n queryModel: string,\n currentData: any,\n model: string,\n args: { where: object; create: any; update: any },\n schema: SchemaDef,\n logging: Logger | undefined,\n) {\n let updated = false;\n let resultData = currentData;\n\n if (Array.isArray(resultData)) {\n // check if we should create or update\n const foundIndex = resultData.findIndex((x) => idFieldsMatch(model, x, args.where, schema));\n if (foundIndex >= 0) {\n const updateResult = updateMutate(\n queryModel,\n resultData[foundIndex],\n model,\n { where: args.where, data: args.update },\n schema,\n logging,\n );\n if (updateResult) {\n // replace the found item with updated item\n resultData = [...resultData.slice(0, foundIndex), updateResult, ...resultData.slice(foundIndex + 1)];\n updated = true;\n }\n } else {\n const createResult = createMutate(queryModel, resultData, args.create, schema, logging);\n if (createResult) {\n resultData = createResult;\n updated = true;\n }\n }\n } else {\n // try update only\n const updateResult = updateMutate(\n queryModel,\n resultData,\n model,\n { where: args.where, data: args.update },\n schema,\n logging,\n );\n if (updateResult) {\n resultData = updateResult;\n updated = true;\n }\n }\n\n return updated ? resultData : undefined;\n}\n\nfunction deleteMutate(\n queryModel: string,\n currentData: any,\n mutateModel: string,\n mutateArgs: any,\n schema: SchemaDef,\n logging: Logger | undefined,\n) {\n // TODO: handle mutation of nested reads?\n\n if (!currentData || !mutateArgs) {\n return undefined;\n }\n\n if (queryModel !== mutateModel) {\n return undefined;\n }\n\n let updated = false;\n let result = currentData;\n\n if (Array.isArray(currentData)) {\n for (const item of currentData) {\n if (idFieldsMatch(mutateModel, item, mutateArgs, schema)) {\n result = (result as unknown[]).filter((x) => x !== item);\n updated = true;\n if (logging) {\n log(logging, `Applying optimistic delete for ${queryModel}: ${JSON.stringify(item)}`);\n }\n }\n }\n } else {\n if (idFieldsMatch(mutateModel, currentData, mutateArgs, schema)) {\n result = null;\n updated = true;\n if (logging) {\n log(logging, `Applying optimistic delete for ${queryModel}: ${JSON.stringify(currentData)}`);\n }\n }\n }\n\n return updated ? result : undefined;\n}\n\nfunction idFieldsMatch(model: string, x: any, y: any, schema: SchemaDef) {\n if (!x || !y || typeof x !== 'object' || typeof y !== 'object') {\n return false;\n }\n const idFields = getIdFields(schema, model);\n if (idFields.length === 0) {\n return false;\n }\n return idFields.every((f) => x[f.name] === y[f.name]);\n}\n\nfunction assignForeignKeyFields(field: FieldDef, resultData: any, mutationData: any) {\n // convert \"connect\" like `{ connect: { id: '...' } }` to foreign key fields\n // assignment: `{ userId: '...' }`\n if (!mutationData?.connect) {\n return;\n }\n\n if (!field.relation?.fields || !field.relation.references) {\n return;\n }\n\n for (const [idField, fkField] of zip(field.relation.references, field.relation.fields)) {\n if (idField in mutationData.connect) {\n resultData[fkField] = mutationData.connect[idField];\n }\n }\n}\n\nfunction getIdFields(schema: SchemaDef, model: string) {\n return (schema.models[model]?.idFields ?? []).map((f) => schema.models[model]!.fields[f]!);\n}\n","import type { SchemaDef } from '@zenstackhq/schema';\nimport { log, type Logger } from './logging';\nimport { applyMutation } from './mutator';\nimport type { ORMWriteActionType, QueryInfo } from './types';\n\n/**\n * Custom optimistic data provider. It takes query information (usually fetched from query cache)\n * and returns a verdict on how to optimistically update the query data.\n *\n * @param args Arguments.\n * @param args.queryModel The model of the query.\n * @param args.queryOperation The operation of the query, `findMany`, `count`, etc.\n * @param args.queryArgs The arguments of the query.\n * @param args.currentData The current cache data for the query.\n * @param args.mutationArgs The arguments of the mutation.\n */\nexport type OptimisticDataProvider = (args: {\n queryModel: string;\n queryOperation: string;\n queryArgs: any;\n currentData: any;\n mutationArgs: any;\n}) => OptimisticDataProviderResult | Promise<OptimisticDataProviderResult>;\n\n/**\n * Result of optimistic data provider.\n */\nexport type OptimisticDataProviderResult = {\n /**\n * Kind of the result.\n * - Update: use the `data` field to update the query cache.\n * - Skip: skip the optimistic update for this query.\n * - ProceedDefault: proceed with the default optimistic update.\n */\n kind: 'Update' | 'Skip' | 'ProceedDefault';\n\n /**\n * Data to update the query cache. Only applicable if `kind` is 'Update'.\n *\n * If the data is an object with fields updated, it should have a `$optimistic`\n * field set to `true`. If it's an array and an element object is created or updated,\n * the element should have a `$optimistic` field set to `true`.\n */\n data?: any;\n};\n\n/**\n * Options for optimistic update.\n */\nexport type OptimisticUpdateOptions = {\n /**\n * A custom optimistic data provider.\n */\n optimisticDataProvider?: OptimisticDataProvider;\n};\n\n/**\n * Creates a function that performs optimistic updates for queries potentially\n * affected by the given mutation operation.\n *\n * @param model Model under mutation.\n * @param operation Mutation operation (e.g, `update`).\n * @param schema The schema.\n * @param options Optimistic update options.\n * @param getAllQueries Callback to get all cached queries.\n * @param logging Logging option.\n */\nexport function createOptimisticUpdater(\n model: string,\n operation: string,\n schema: SchemaDef,\n options: OptimisticUpdateOptions,\n getAllQueries: () => readonly QueryInfo[],\n logging: Logger | undefined,\n) {\n return async (...args: unknown[]) => {\n const [mutationArgs] = args;\n\n for (const queryInfo of getAllQueries()) {\n const logInfo = JSON.stringify({\n model: queryInfo.model,\n operation: queryInfo.operation,\n args: queryInfo.args,\n });\n\n if (!queryInfo.optimisticUpdate) {\n if (logging) {\n log(logging, `Skipping optimistic update for ${logInfo} due to opt-out`);\n }\n continue;\n }\n\n if (options.optimisticDataProvider) {\n const providerResult = await options.optimisticDataProvider({\n queryModel: queryInfo.model,\n queryOperation: queryInfo.operation,\n queryArgs: queryInfo.args,\n currentData: queryInfo.data,\n mutationArgs,\n });\n\n if (providerResult?.kind === 'Skip') {\n // skip\n if (logging) {\n log(logging, `Skipping optimistic updating due to provider result: ${logInfo}`);\n }\n continue;\n } else if (providerResult?.kind === 'Update') {\n // update cache\n if (logging) {\n log(logging, `Optimistically updating due to provider result: ${logInfo}`);\n }\n queryInfo.updateData(providerResult.data, true);\n continue;\n }\n }\n\n // proceed with default optimistic update\n const mutatedData = await applyMutation(\n queryInfo.model,\n queryInfo.operation,\n queryInfo.data,\n model,\n operation as ORMWriteActionType,\n mutationArgs,\n schema,\n logging,\n );\n\n if (mutatedData !== undefined) {\n // mutation applicable to this query, update cache\n if (logging) {\n log(logging, `Optimistically updating due to mutation \"${model}.${operation}\": ${logInfo}`);\n }\n queryInfo.updateData(mutatedData, true);\n }\n }\n };\n}\n"],"mappings":"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","names":["DEFAULT_QUERY_ENDPOINT","log","logger","message","console","NestedReadVisitor","schema","callback","doVisit","model","field","kind","args","r","selectInclude","nextKind","select","include","k","v","Object","entries","models","fields","type","visit","undefined","enumerate","ORMWriteActions","NestedWriteVisitor","schema","callback","isWriteAction","value","ORMWriteActions","includes","visit","model","action","args","topData","data","where","doVisit","undefined","parent","field","nestingPath","toplevel","context","pushNewContext","unique","item","enumerateReverse","newContext","callbackResult","create","subPayload","visitSubPayload","createMany","connectOrCreate","connect","disconnect","set","update","updateMany","upsert","delete","deleteMany","Error","payload","enumerate","Object","keys","fieldDef","models","fields","relation","subAction","subData","entries","type","Array","isArray","i","length","getReadModels","model","schema","args","result","Set","add","visitor","NestedReadVisitor","field","visit","getMutatedModels","operation","mutationArgs","addModel","addCascades","cascades","visited","collectDeleteCascades","forEach","m","NestedWriteVisitor","create","createMany","connectOrCreate","connect","disconnect","set","update","updateMany","upsert","delete","deleteMany","getBaseRecursively","has","modelDef","models","modelName","Object","entries","fieldDef","values","fields","relation","onDelete","type","baseModel","createInvalidator","model","operation","schema","invalidator","logging","args","_","variables","predicate","getInvalidationPredicate","mutationArgs","mutatedModels","getMutatedModels","includes","log","JSON","stringify","findNestedRead","visitingModel","targetModels","modelsRead","getReadModels","some","m","clone","enumerate","invariant","zip","applyMutation","queryModel","queryOp","queryData","mutationModel","mutationOp","mutationArgs","schema","logging","Array","isArray","undefined","startsWith","doApplyMutation","resultData","updated","visitor","NestedWriteVisitor","create","model","args","r","createMutate","createMany","data","oneArg","enumerate","update","updateMutate","upsert","where","upsertMutate","delete","deleteMutate","visit","modelFields","models","fields","invariant","arrayCloned","i","length","item","$optimistic","currentData","key","value","Object","entries","fieldDef","relation","type","newData","insert","newDataFields","keys","forEach","name","field","assignForeignKeyFields","includes","clone","defaultAttr","attributes","find","attr","some","Date","defaultArg","kind","idFields","getIdFields","f","currMax","Math","max","map","idv","parseInt","isNaN","crypto","randomUUID","log","JSON","stringify","mutateModel","mutateArgs","idFieldsMatch","fieldInfo","connect","foundIndex","findIndex","x","updateResult","slice","createResult","result","filter","y","every","mutationData","references","idField","fkField","zip","createOptimisticUpdater","model","operation","schema","options","getAllQueries","logging","args","mutationArgs","queryInfo","logInfo","JSON","stringify","optimisticUpdate","log","optimisticDataProvider","providerResult","queryModel","queryOperation","queryArgs","currentData","data","kind","updateData","mutatedData","applyMutation","undefined"]}
@@ -0,0 +1,4 @@
1
+ import config from '@zenstackhq/eslint-config/base.js';
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default config;
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@zenstackhq/client-helpers",
3
+ "version": "3.1.0",
4
+ "description": "Helpers for implementing clients that consume ZenStack's CRUD service",
5
+ "type": "module",
6
+ "author": "ZenStack Team",
7
+ "license": "MIT",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./fetch": {
14
+ "types": "./dist/fetch.d.ts",
15
+ "default": "./dist/fetch.js"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "decimal.js": "^10.4.3",
20
+ "superjson": "^2.2.3",
21
+ "@zenstackhq/common-helpers": "3.1.0",
22
+ "@zenstackhq/schema": "3.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@zenstackhq/eslint-config": "3.1.0",
26
+ "@zenstackhq/language": "3.1.0",
27
+ "@zenstackhq/sdk": "3.1.0",
28
+ "@zenstackhq/orm": "3.1.0",
29
+ "@zenstackhq/typescript-config": "3.1.0",
30
+ "@zenstackhq/vitest-config": "3.1.0"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc --noEmit && tsup-node && pnpm test:typecheck",
34
+ "watch": "tsup-node --watch",
35
+ "lint": "eslint src --ext ts",
36
+ "test": "vitest run",
37
+ "test:typecheck": "tsc --noEmit --project tsconfig.test.json",
38
+ "pack": "pnpm pack"
39
+ }
40
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * The default query endpoint.
3
+ */
4
+ export const DEFAULT_QUERY_ENDPOINT = '/api/model';
package/src/fetch.ts ADDED
@@ -0,0 +1,107 @@
1
+ import { lowerCaseFirst } from '@zenstackhq/common-helpers';
2
+ import Decimal from 'decimal.js';
3
+ import SuperJSON from 'superjson';
4
+ import type { QueryError } from './types';
5
+
6
+ /**
7
+ * Function signature for `fetch`.
8
+ */
9
+ export type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;
10
+
11
+ /**
12
+ * A fetcher function that uses fetch API to make HTTP requests and automatically unmarshals
13
+ * the response using superjson.
14
+ */
15
+ export async function fetcher<R>(url: string, options?: RequestInit, customFetch?: FetchFn): Promise<R> {
16
+ const _fetch = customFetch ?? fetch;
17
+ const res = await _fetch(url, options);
18
+ if (!res.ok) {
19
+ const errData = unmarshal(await res.text());
20
+ if (errData.error?.rejectedByPolicy && errData.error?.rejectReason === 'cannot-read-back') {
21
+ // policy doesn't allow mutation result to be read back, just return undefined
22
+ return undefined as any;
23
+ }
24
+ const error: QueryError = new Error('An error occurred while fetching the data.');
25
+ error.info = errData.error;
26
+ error.status = res.status;
27
+ throw error;
28
+ }
29
+
30
+ const textResult = await res.text();
31
+ try {
32
+ return unmarshal(textResult).data as R;
33
+ } catch (err) {
34
+ console.error(`Unable to deserialize data:`, textResult);
35
+ throw err;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Makes a URL for the given endpoint, model, operation, and args that matches the RPC-style server API.
41
+ */
42
+ export function makeUrl(endpoint: string, model: string, operation: string, args?: unknown) {
43
+ const baseUrl = `${endpoint}/${lowerCaseFirst(model)}/${operation}`;
44
+ if (!args) {
45
+ return baseUrl;
46
+ }
47
+
48
+ const { data, meta } = serialize(args);
49
+ let result = `${baseUrl}?q=${encodeURIComponent(JSON.stringify(data))}`;
50
+ if (meta) {
51
+ result += `&meta=${encodeURIComponent(JSON.stringify({ serialization: meta }))}`;
52
+ }
53
+ return result;
54
+ }
55
+
56
+ SuperJSON.registerCustom<Decimal, string>(
57
+ {
58
+ isApplicable: (v): v is Decimal =>
59
+ v instanceof Decimal ||
60
+ // interop with decimal.js
61
+ v?.toStringTag === '[object Decimal]',
62
+ serialize: (v) => v.toJSON(),
63
+ deserialize: (v) => new Decimal(v),
64
+ },
65
+ 'Decimal',
66
+ );
67
+
68
+ /**
69
+ * Serialize the given value with superjson
70
+ */
71
+ export function serialize(value: unknown): { data: unknown; meta: unknown } {
72
+ const { json, meta } = SuperJSON.serialize(value);
73
+ return { data: json, meta };
74
+ }
75
+
76
+ /**
77
+ * Deserialize the given value with superjson using the given metadata
78
+ */
79
+ export function deserialize(value: unknown, meta: any): unknown {
80
+ return SuperJSON.deserialize({ json: value as any, meta });
81
+ }
82
+
83
+ /**
84
+ * Marshal the given value to a string using superjson
85
+ */
86
+ export function marshal(value: unknown) {
87
+ const { data, meta } = serialize(value);
88
+ if (meta) {
89
+ return JSON.stringify({ ...(data as any), meta: { serialization: meta } });
90
+ } else {
91
+ return JSON.stringify(data);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Unmarshal the given string value using superjson, assuming the value is a JSON stringified
97
+ * object containing the serialized data and serialization metadata.
98
+ */
99
+ export function unmarshal(value: string) {
100
+ const parsed = JSON.parse(value);
101
+ if (typeof parsed === 'object' && parsed?.data && parsed?.meta?.serialization) {
102
+ const deserializedData = deserialize(parsed.data, parsed.meta.serialization);
103
+ return { ...parsed, data: deserializedData };
104
+ } else {
105
+ return parsed;
106
+ }
107
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './constants';
2
+ export * from './invalidation';
3
+ export * from './logging';
4
+ export * from './mutator';
5
+ export * from './nested-read-visitor';
6
+ export * from './nested-write-visitor';
7
+ export * from './optimistic';
8
+ export * from './query-analysis';
9
+ export * from './types';
@@ -0,0 +1,89 @@
1
+ import type { SchemaDef } from '@zenstackhq/schema';
2
+ import { log, type Logger } from './logging';
3
+ import { getMutatedModels, getReadModels } from './query-analysis';
4
+ import type { MaybePromise, ORMWriteActionType } from './types';
5
+
6
+ /**
7
+ * Type for a predicate that determines whether a query should be invalidated.
8
+ */
9
+ export type InvalidationPredicate = ({ model, args }: { model: string; args: unknown }) => boolean;
10
+
11
+ /**
12
+ * Type for a function that invalidates queries matching the given predicate.
13
+ */
14
+ export type InvalidateFunc = (predicate: InvalidationPredicate) => MaybePromise<void>;
15
+
16
+ /**
17
+ * Create a function that invalidates queries affected by the given mutation operation.
18
+ *
19
+ * @param model Model under mutation.
20
+ * @param operation Mutation operation (e.g, `update`).
21
+ * @param schema The schema.
22
+ * @param invalidator Function to invalidate queries matching a predicate. It should internally
23
+ * enumerate all query cache entries and invalidate those for which the predicate returns true.
24
+ * @param logging Logging option.
25
+ */
26
+ export function createInvalidator(
27
+ model: string,
28
+ operation: string,
29
+ schema: SchemaDef,
30
+ invalidator: InvalidateFunc,
31
+ logging: Logger | undefined,
32
+ ) {
33
+ return async (...args: unknown[]) => {
34
+ const [_, variables] = args;
35
+ const predicate = await getInvalidationPredicate(
36
+ model,
37
+ operation as ORMWriteActionType,
38
+ variables,
39
+ schema,
40
+ logging,
41
+ );
42
+ await invalidator(predicate);
43
+ };
44
+ }
45
+
46
+ // gets a predicate for evaluating whether a query should be invalidated
47
+ async function getInvalidationPredicate(
48
+ model: string,
49
+ operation: ORMWriteActionType,
50
+ mutationArgs: any,
51
+ schema: SchemaDef,
52
+ logging: Logger | undefined,
53
+ ): Promise<InvalidationPredicate> {
54
+ const mutatedModels = await getMutatedModels(model, operation, mutationArgs, schema);
55
+
56
+ return ({ model, args }) => {
57
+ if (mutatedModels.includes(model)) {
58
+ // direct match
59
+ if (logging) {
60
+ log(
61
+ logging,
62
+ `Marking "${model}" query for invalidation due to mutation "${operation}", query args: ${JSON.stringify(args)}`,
63
+ );
64
+ }
65
+ return true;
66
+ }
67
+
68
+ if (args) {
69
+ // traverse query args to find nested reads that match the model under mutation
70
+ if (findNestedRead(model, mutatedModels, schema, args)) {
71
+ if (logging) {
72
+ log(
73
+ logging,
74
+ `Marking "${model}" query for invalidation due to mutation "${operation}", query args: ${JSON.stringify(args)}`,
75
+ );
76
+ }
77
+ return true;
78
+ }
79
+ }
80
+
81
+ return false;
82
+ };
83
+ }
84
+
85
+ // find nested reads that match the given models
86
+ function findNestedRead(visitingModel: string, targetModels: string[], schema: SchemaDef, args: any) {
87
+ const modelsRead = getReadModels(visitingModel, schema, args);
88
+ return targetModels.some((m) => modelsRead.includes(m));
89
+ }
package/src/logging.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Logger configuration. `true` enables console logging. A function can be provided for custom logging.
3
+ */
4
+ export type Logger = boolean | ((message: string) => void);
5
+
6
+ /**
7
+ * Logs a message using the provided logger.
8
+ */
9
+ export function log(logger: Logger, message: string) {
10
+ if (typeof logger === 'function') {
11
+ logger(message);
12
+ } else if (logger) {
13
+ console.log(message);
14
+ }
15
+ }