syncorejs 0.2.2 → 0.2.3
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.
- package/dist/_vendor/cli/app.d.mts.map +1 -1
- package/dist/_vendor/cli/app.mjs +8 -5
- package/dist/_vendor/cli/app.mjs.map +1 -1
- package/dist/_vendor/cli/context.mjs.map +1 -1
- package/dist/_vendor/cli/dev-session.mjs.map +1 -1
- package/dist/_vendor/cli/doctor.mjs.map +1 -1
- package/dist/_vendor/cli/errors.mjs.map +1 -1
- package/dist/_vendor/cli/help.mjs.map +1 -1
- package/dist/_vendor/cli/index.mjs +9 -2
- package/dist/_vendor/cli/index.mjs.map +1 -1
- package/dist/_vendor/cli/messages.mjs.map +1 -1
- package/dist/_vendor/cli/preflight.mjs.map +1 -1
- package/dist/_vendor/cli/project.mjs +20 -20
- package/dist/_vendor/cli/project.mjs.map +1 -1
- package/dist/_vendor/cli/render.mjs.map +1 -1
- package/dist/_vendor/cli/targets.mjs.map +1 -1
- package/dist/_vendor/core/cli.d.mts +8 -2
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +238 -64
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/devtools-auth.mjs.map +1 -1
- package/dist/_vendor/core/runtime/components.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/components.mjs.map +1 -1
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +130 -23
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.d.mts +388 -6
- package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs +72 -1
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/id.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/id.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +11 -5
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +123 -20
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +56 -8
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +49 -14
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +4 -7
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs +76 -1
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +1 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +4 -3
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +4 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -1
- package/dist/_vendor/core/runtime/runtime.d.mts +1040 -9
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +63 -0
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/core/transport.d.mts +2 -0
- package/dist/_vendor/core/transport.d.mts.map +1 -1
- package/dist/_vendor/core/transport.mjs +33 -24
- package/dist/_vendor/core/transport.mjs.map +1 -1
- package/dist/_vendor/devtools-protocol/index.d.ts +149 -4
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js.map +1 -1
- package/dist/_vendor/next/config.d.ts +3 -4
- package/dist/_vendor/next/config.d.ts.map +1 -1
- package/dist/_vendor/next/config.js +37 -19
- package/dist/_vendor/next/config.js.map +1 -1
- package/dist/_vendor/next/index.d.ts +109 -29
- package/dist/_vendor/next/index.d.ts.map +1 -1
- package/dist/_vendor/next/index.js +77 -17
- package/dist/_vendor/next/index.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +146 -27
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +76 -10
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js +16 -0
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-node/index.d.mts +173 -9
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +225 -94
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +41 -0
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
- package/dist/_vendor/platform-web/external-change.js +30 -0
- package/dist/_vendor/platform-web/external-change.js.map +1 -1
- package/dist/_vendor/platform-web/index.d.ts +307 -35
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +189 -23
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.d.ts +12 -0
- package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.js +10 -0
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.d.ts +13 -0
- package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
- package/dist/_vendor/platform-web/opfs.js +12 -0
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.d.ts +54 -0
- package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
- package/dist/_vendor/platform-web/persistence.js +15 -0
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.d.ts +1 -2
- package/dist/_vendor/platform-web/react.d.ts.map +1 -1
- package/dist/_vendor/platform-web/react.js +2 -4
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/sqljs.js +10 -1
- package/dist/_vendor/platform-web/sqljs.js.map +1 -1
- package/dist/_vendor/platform-web/web-sqljs-wasm.js +8 -0
- package/dist/_vendor/platform-web/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-web/worker.d.ts +60 -9
- package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
- package/dist/_vendor/platform-web/worker.js +37 -4
- package/dist/_vendor/platform-web/worker.js.map +1 -1
- package/dist/_vendor/react/index.d.ts +196 -13
- package/dist/_vendor/react/index.d.ts.map +1 -1
- package/dist/_vendor/react/index.js +208 -17
- package/dist/_vendor/react/index.js.map +1 -1
- package/dist/_vendor/schema/definition.d.ts +129 -0
- package/dist/_vendor/schema/definition.d.ts.map +1 -1
- package/dist/_vendor/schema/definition.js +99 -0
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/planner.d.ts.map +1 -1
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.d.ts +180 -4
- package/dist/_vendor/schema/validators.d.ts.map +1 -1
- package/dist/_vendor/schema/validators.js +35 -1
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/_vendor/svelte/index.d.ts +205 -7
- package/dist/_vendor/svelte/index.d.ts.map +1 -1
- package/dist/_vendor/svelte/index.js +199 -6
- package/dist/_vendor/svelte/index.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +24 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import {\n createContext,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n useState\n} from \"react\";\nimport type {\n FunctionArgs,\n FunctionReference,\n FunctionResult,\n PaginationOptions,\n PaginationResult,\n SyncoreClient,\n SyncorePaginatedQueryStatus,\n SyncoreQueryState,\n SyncoreRuntimeStatus,\n SyncoreWatch,\n UsePaginatedQueryResult\n} from \"@syncore/core\";\n\ntype ManagedSyncoreWatch<TResult> = SyncoreWatch<TResult> & {\n dispose?: () => void;\n};\n\ntype OptionalArgsTuple<TArgs> =\n Record<never, never> extends TArgs ? [args?: TArgs] : [args: TArgs];\n\ntype QueryRequestInput<\n TReference extends FunctionReference<\"query\"> = FunctionReference<\"query\">\n> = Record<never, never> extends FunctionArgs<TReference>\n ? {\n query: TReference;\n args?: FunctionArgs<TReference> | Skip;\n }\n : {\n query: TReference;\n args: FunctionArgs<TReference> | Skip;\n };\n\ntype QueriesRequestInput = Record<string, QueryRequestInput>;\n\ntype QueryStateForEntry<TEntry> = TEntry extends QueryRequestInput<\n infer TReference\n>\n ? SyncoreQueryState<FunctionResult<TReference>>\n : never;\n\nexport type UseQueriesResult<TEntries extends QueriesRequestInput> = {\n [TKey in keyof TEntries]: QueryStateForEntry<TEntries[TKey]>;\n};\n\ntype PaginatedQueryReference = FunctionReference<\n \"query\",\n Record<string, unknown>,\n PaginationResult<unknown>\n>;\n\ntype PaginatedQueryArgs<TReference extends FunctionReference<\"query\">> =\n FunctionArgs<TReference> extends { paginationOpts: PaginationOptions }\n ? Omit<FunctionArgs<TReference>, \"paginationOpts\">\n : never;\n\ntype PaginatedQueryItem<TReference extends FunctionReference<\"query\">> =\n FunctionResult<TReference> extends PaginationResult<infer TItem>\n ? TItem\n : never;\n\ntype QuerySnapshot<TResult> = {\n data: TResult | undefined;\n error: Error | undefined;\n};\n\ntype NormalizedQueryEntry = {\n key: string;\n referenceName: string;\n args: Record<string, unknown>;\n skipped: boolean;\n};\n\ntype PaginatedQueryInternalState = {\n requestKey: string;\n nextPageKey: number;\n pages: Array<{\n key: string;\n cursor: string | null;\n numItems: number;\n }>;\n};\n\ntype QueryObserverRecord = {\n requestKey: string;\n snapshot: QuerySnapshot<unknown>;\n unsubscribe: () => void;\n watch?: ManagedSyncoreWatch<unknown>;\n};\n\n/**\n * Pass `\"skip\"` as the args argument to `useQuery`, `useQueryState`,\n * `useQueries`, or `usePaginatedQuery` to suppress the subscription entirely.\n */\nexport const skip = \"skip\" as const;\ntype Skip = typeof skip;\n\nconst defaultRuntimeStatus: SyncoreRuntimeStatus = {\n kind: \"starting\",\n reason: \"booting\"\n};\n\nconst SyncoreContext = createContext<SyncoreClient | null>(null);\n\n/**\n * Provide a Syncore client to React descendants.\n */\nexport function SyncoreProvider({\n client,\n children\n}: {\n client: SyncoreClient;\n children: ReactNode;\n}) {\n return (\n <SyncoreContext.Provider value={client}>{children}</SyncoreContext.Provider>\n );\n}\n\n/**\n * Read the active Syncore client from React context.\n */\nexport function useSyncore(): SyncoreClient {\n const client = useContext(SyncoreContext);\n if (!client) {\n throw new Error(\"SyncoreProvider is missing from the React tree.\");\n }\n return client;\n}\n\n/**\n * Subscribe to the active Syncore client's runtime lifecycle status.\n */\nexport function useSyncoreStatus(): SyncoreRuntimeStatus {\n const client = useSyncore();\n const watch = useMemo(\n () => client.watchRuntimeStatus() as ManagedSyncoreWatch<SyncoreRuntimeStatus>,\n [client]\n );\n const [status, setStatus] = useState<SyncoreRuntimeStatus>(() =>\n readRuntimeStatusSnapshot(watch)\n );\n\n useEffect(() => {\n const sync = () => {\n setStatus(readRuntimeStatusSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch]);\n\n useEffect(\n () => () => {\n watch.dispose?.();\n },\n [watch]\n );\n\n return status;\n}\n\n/**\n * Load a reactive Syncore query and return only the data for the common case.\n */\nexport function useQuery<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): TResult | undefined {\n const state = useQueryState(reference, ...(args as OptionalArgsTuple<TArgs> | [Skip]));\n if (state.error) {\n throw state.error;\n }\n return state.data;\n}\n\n/**\n * Load a reactive Syncore query and keep the full local state.\n */\nexport function useQueryState<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): SyncoreQueryState<TResult> {\n const isSkipped = args[0] === skip;\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const watch = useManagedQueryWatch(\n client,\n reference,\n isSkipped\n ? undefined\n : normalizeOptionalArgs(args as OptionalArgsTuple<TArgs>),\n isSkipped\n );\n const [snapshot, setSnapshot] = useState<QuerySnapshot<TResult>>(() =>\n isSkipped ? noOpSnapshot : readWatchSnapshot(watch)\n );\n\n useEffect(() => {\n if (isSkipped) {\n setSnapshot(noOpSnapshot);\n return;\n }\n const sync = () => {\n setSnapshot(readWatchSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch, isSkipped]);\n\n return toQueryState(snapshot, runtimeStatus, isSkipped);\n}\n\n/**\n * Construct a stable function that executes a Syncore mutation.\n */\nexport function useMutation<TArgs, TResult>(\n reference: FunctionReference<\"mutation\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.mutation(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Construct a stable function that executes a Syncore action.\n */\nexport function useAction<TArgs, TResult>(\n reference: FunctionReference<\"action\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.action(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Load a keyed set of Syncore queries at once with per-entry state.\n */\nexport function useQueries<TEntries extends QueriesRequestInput>(\n entries: TEntries\n): UseQueriesResult<TEntries> {\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const entriesKey = stableStringify(\n Object.entries(entries)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => ({\n key,\n referenceName: entry.query.name,\n skipped: entry.args === skip,\n args:\n entry.args === skip\n ? {}\n : normalizeOptionalArgs([entry.args ?? {}] as [] | [unknown])\n }))\n );\n const normalizedEntries = useMemo(\n () => JSON.parse(entriesKey) as NormalizedQueryEntry[],\n [entriesKey]\n );\n const [observer] = useState(() => new ReactQueriesObserver(client));\n const [, setVersion] = useState(0);\n\n if (observer.client !== client) {\n observer.replaceClient(client);\n }\n\n useEffect(() => () => observer.destroy(), [observer]);\n\n useEffect(() => {\n observer.setEntries(normalizedEntries);\n setVersion((value) => value + 1);\n return observer.subscribe(() => {\n setVersion((value) => value + 1);\n });\n }, [normalizedEntries, observer]);\n\n const snapshot = observer.getSnapshot(normalizedEntries);\n\n return useMemo(() => {\n return Object.fromEntries(\n normalizedEntries.map((entry) => [\n entry.key,\n toQueryState(\n snapshot[entry.key] ?? noOpSnapshot,\n runtimeStatus,\n entry.skipped\n )\n ])\n ) as UseQueriesResult<TEntries>;\n }, [normalizedEntries, runtimeStatus, snapshot]);\n}\n\n/**\n * Load a paginated Syncore query as a growing reactive list.\n */\nexport function usePaginatedQuery<TReference extends PaginatedQueryReference>(\n reference: TReference,\n args: PaginatedQueryArgs<TReference> | Skip,\n options: {\n initialNumItems: number;\n }\n): UsePaginatedQueryResult<PaginatedQueryItem<TReference>> {\n if (\n typeof options.initialNumItems !== \"number\" ||\n options.initialNumItems <= 0\n ) {\n throw new Error(\n `options.initialNumItems must be a positive number. Received ${String(\n options.initialNumItems\n )}.`\n );\n }\n\n const runtimeStatus = useSyncoreStatus();\n const isSkipped = args === skip;\n const normalizedArgs = isSkipped ? {} : (args ?? {});\n const requestKey = stableStringify({\n referenceName: reference.name,\n args: normalizedArgs,\n initialNumItems: options.initialNumItems,\n skipped: isSkipped\n });\n const createInitialState = useMemo(\n () => () =>\n ({\n requestKey,\n nextPageKey: 1,\n pages: isSkipped\n ? []\n : [\n {\n key: \"0\",\n cursor: null,\n numItems: options.initialNumItems\n }\n ]\n }) satisfies PaginatedQueryInternalState,\n [isSkipped, options.initialNumItems, requestKey]\n );\n const [state, setState] = useState<PaginatedQueryInternalState>(\n createInitialState\n );\n\n let currentState = state;\n if (currentState.requestKey !== requestKey) {\n currentState = createInitialState();\n setState(currentState);\n }\n\n const pageQueries = useMemo(() => {\n const requests: Record<string, QueryRequestInput> = {};\n for (const page of currentState.pages) {\n requests[page.key] = {\n query: reference,\n args: {\n ...(normalizedArgs as Record<string, unknown>),\n paginationOpts: {\n cursor: page.cursor,\n numItems: page.numItems\n }\n }\n };\n }\n return requests;\n }, [currentState.pages, normalizedArgs, reference]);\n const pageStates = useQueries(pageQueries);\n\n const derived = useMemo(() => {\n const pages: Array<PaginationResult<PaginatedQueryItem<TReference>>> = [];\n let error: Error | undefined;\n\n for (const page of currentState.pages) {\n const pageState =\n pageStates[page.key as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined;\n if (!pageState || pageState.status === \"loading\") {\n break;\n }\n if (pageState.status === \"error\") {\n error = pageState.error;\n break;\n }\n if (pageState.data) {\n pages.push(pageState.data);\n }\n }\n\n const results = pages.flatMap((page) => page.page);\n const lastLoadedPage = pages.at(-1);\n const lastRequestedKey = currentState.pages.at(-1)?.key;\n const lastRequestedState = lastRequestedKey\n ? (pageStates[lastRequestedKey as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined)\n : undefined;\n const isLoading = !isSkipped && pages.length === 0 && !error;\n const isLoadingMore =\n currentState.pages.length > pages.length ||\n (!!lastRequestedState && lastRequestedState.status === \"loading\" && pages.length > 0);\n const hasMore = !!lastLoadedPage && !lastLoadedPage.isDone;\n const status: SyncorePaginatedQueryStatus = error\n ? \"error\"\n : isSkipped\n ? \"ready\"\n : isLoading\n ? \"loading\"\n : isLoadingMore\n ? \"loadingMore\"\n : hasMore\n ? \"ready\"\n : \"exhausted\";\n\n return {\n pages,\n results,\n error,\n isLoading,\n isLoadingMore,\n hasMore,\n cursor: lastLoadedPage?.cursor ?? null,\n status\n };\n }, [currentState.pages, isSkipped, pageStates]);\n\n return {\n ...derived,\n runtimeStatus,\n loadMore(numItems = options.initialNumItems) {\n if (\n isSkipped ||\n derived.error ||\n derived.isLoadingMore ||\n !derived.hasMore ||\n !derived.cursor\n ) {\n return;\n }\n\n setState((previous) => ({\n ...previous,\n nextPageKey: previous.nextPageKey + 1,\n pages: [\n ...previous.pages,\n {\n key: String(previous.nextPageKey),\n cursor: derived.cursor,\n numItems\n }\n ]\n }));\n }\n };\n}\n\nconst noOpSnapshot: QuerySnapshot<never> = {\n data: undefined,\n error: undefined\n};\n\nconst noOpWatch: ManagedSyncoreWatch<never> = {\n onUpdate: () => () => undefined,\n localQueryResult: () => undefined,\n localQueryError: () => undefined\n};\n\nfunction useManagedQueryWatch<TArgs, TResult>(\n client: SyncoreClient,\n reference: FunctionReference<\"query\", TArgs, TResult>,\n args?: TArgs,\n isSkipped = false\n): ManagedSyncoreWatch<TResult> {\n const argsKey = isSkipped ? skip : stableStringify(args ?? {});\n const normalizedArgs = useMemo(\n () => (isSkipped ? undefined : (JSON.parse(argsKey) as TArgs)),\n [argsKey, isSkipped]\n );\n const watch = useMemo<ManagedSyncoreWatch<TResult>>(\n () =>\n isSkipped\n ? noOpWatch\n : (client.watchQuery(\n reference,\n normalizedArgs as TArgs\n ) as ManagedSyncoreWatch<TResult>),\n [client, isSkipped, normalizedArgs, reference]\n );\n\n useEffect(\n () => () => {\n if (!isSkipped) {\n watch.dispose?.();\n }\n },\n [isSkipped, watch]\n );\n\n return watch;\n}\n\nfunction normalizeOptionalArgs<TArgs>(\n args: [] | [TArgs] | readonly unknown[]\n): TArgs {\n return (args[0] ?? {}) as TArgs;\n}\n\nfunction readWatchSnapshot<TResult>(\n watch: SyncoreWatch<TResult>\n): QuerySnapshot<TResult> {\n return {\n data: watch.localQueryResult(),\n error: watch.localQueryError()\n };\n}\n\nfunction readQueriesSnapshot(\n records: Array<{\n key: string;\n snapshot: QuerySnapshot<unknown>;\n }>\n): Record<string, QuerySnapshot<unknown>> {\n return Object.fromEntries(\n records.map((entry) => [entry.key, entry.snapshot])\n );\n}\n\nfunction readRuntimeStatusSnapshot(\n watch: SyncoreWatch<SyncoreRuntimeStatus>\n): SyncoreRuntimeStatus {\n return watch.localQueryResult() ?? defaultRuntimeStatus;\n}\n\nfunction toQueryState<TResult>(\n snapshot: QuerySnapshot<TResult>,\n runtimeStatus: SyncoreRuntimeStatus,\n isSkipped: boolean\n): SyncoreQueryState<TResult> {\n if (isSkipped) {\n return {\n data: undefined,\n error: undefined,\n status: \"skipped\",\n runtimeStatus,\n isLoading: false,\n isError: false,\n isReady: false\n };\n }\n\n const status =\n snapshot.error !== undefined\n ? \"error\"\n : snapshot.data === undefined\n ? \"loading\"\n : \"success\";\n\n return {\n data: snapshot.data,\n error: snapshot.error,\n status,\n runtimeStatus,\n isLoading: status === \"loading\",\n isError: status === \"error\",\n isReady: status === \"success\"\n };\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortValue);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, sortValue(nested)])\n );\n }\n return value;\n}\n\nclass ReactQueriesObserver {\n readonly client: SyncoreClient;\n private readonly listeners = new Set<() => void>();\n private readonly records = new Map<string, QueryObserverRecord>();\n\n constructor(client: SyncoreClient) {\n this.client = client;\n }\n\n replaceClient(client: SyncoreClient): void {\n this.destroy();\n (this as { client: SyncoreClient }).client = client;\n }\n\n setEntries(entries: NormalizedQueryEntry[]): void {\n const activeKeys = new Set(entries.map((entry) => entry.key));\n\n for (const entry of entries) {\n const requestKey = `${entry.referenceName}:${stableStringify(entry.args)}:${String(\n entry.skipped\n )}`;\n const current = this.records.get(entry.key);\n if (current?.requestKey === requestKey) {\n continue;\n }\n\n current?.unsubscribe();\n current?.watch?.dispose?.();\n\n if (entry.skipped) {\n this.records.set(entry.key, {\n requestKey,\n snapshot: noOpSnapshot,\n unsubscribe: () => undefined\n });\n continue;\n }\n\n const watch = this.client.watchQuery(\n { kind: \"query\", name: entry.referenceName },\n entry.args\n ) as ManagedSyncoreWatch<unknown>;\n const record: QueryObserverRecord = {\n requestKey,\n snapshot: readWatchSnapshot(watch),\n unsubscribe: () => undefined,\n watch\n };\n record.unsubscribe = watch.onUpdate(() => {\n record.snapshot = readWatchSnapshot(watch);\n this.notify();\n });\n this.records.set(entry.key, record);\n }\n\n for (const [key, record] of this.records.entries()) {\n if (activeKeys.has(key)) {\n continue;\n }\n record.unsubscribe();\n record.watch?.dispose?.();\n this.records.delete(key);\n }\n }\n\n getSnapshot(entries: NormalizedQueryEntry[]): Record<string, QuerySnapshot<unknown>> {\n return readQueriesSnapshot(\n entries.map((entry) => ({\n key: entry.key,\n snapshot: this.records.get(entry.key)?.snapshot ?? noOpSnapshot\n }))\n );\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n destroy(): void {\n for (const record of this.records.values()) {\n record.unsubscribe();\n record.watch?.dispose?.();\n }\n this.records.clear();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n"],"mappings":";;;;;;;AAsGA,MAAa,OAAO;AAGpB,MAAM,uBAA6C;CACjD,MAAM;CACN,QAAQ;CACT;AAED,MAAM,iBAAiB,cAAoC,KAAK;;;;AAKhE,SAAgB,gBAAgB,EAC9B,QACA,YAIC;AACD,QACE,oBAAC,eAAe,UAAhB;EAAyB,OAAO;EAAS;EAAmC,CAAA;;;;;AAOhF,SAAgB,aAA4B;CAC1C,MAAM,SAAS,WAAW,eAAe;AACzC,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,kDAAkD;AAEpE,QAAO;;;;;AAMT,SAAgB,mBAAyC;CACvD,MAAM,SAAS,YAAY;CAC3B,MAAM,QAAQ,cACN,OAAO,oBAAoB,EACjC,CAAC,OAAO,CACT;CACD,MAAM,CAAC,QAAQ,aAAa,eAC1B,0BAA0B,MAAM,CACjC;AAED,iBAAgB;EACd,MAAM,aAAa;AACjB,aAAU,0BAA0B,MAAM,CAAC;;AAE7C,QAAM;AACN,SAAO,MAAM,SAAS,KAAK;IAC1B,CAAC,MAAM,CAAC;AAEX,uBACc;AACV,QAAM,WAAW;IAEnB,CAAC,MAAM,CACR;AAED,QAAO;;;;;AAMT,SAAgB,SACd,WACA,GAAG,MACkB;CACrB,MAAM,QAAQ,cAAc,WAAW,GAAI,KAA2C;AACtF,KAAI,MAAM,MACR,OAAM,MAAM;AAEd,QAAO,MAAM;;;;;AAMf,SAAgB,cACd,WACA,GAAG,MACyB;CAC5B,MAAM,YAAY,KAAK,OAAO;CAC9B,MAAM,SAAS,YAAY;CAC3B,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,QAAQ,qBACZ,QACA,WACA,YACI,KAAA,IACA,sBAAsB,KAAiC,EAC3D,UACD;CACD,MAAM,CAAC,UAAU,eAAe,eAC9B,YAAY,eAAe,kBAAkB,MAAM,CACpD;AAED,iBAAgB;AACd,MAAI,WAAW;AACb,eAAY,aAAa;AACzB;;EAEF,MAAM,aAAa;AACjB,eAAY,kBAAkB,MAAM,CAAC;;AAEvC,QAAM;AACN,SAAO,MAAM,SAAS,KAAK;IAC1B,CAAC,OAAO,UAAU,CAAC;AAEtB,QAAO,aAAa,UAAU,eAAe,UAAU;;;;;AAMzD,SAAgB,YACd,WACyD;CACzD,MAAM,SAAS,YAAY;AAC3B,SAAQ,GAAG,SAAS,OAAO,SAAS,WAAW,sBAAsB,KAAK,CAAC;;;;;AAM7E,SAAgB,UACd,WACyD;CACzD,MAAM,SAAS,YAAY;AAC3B,SAAQ,GAAG,SAAS,OAAO,OAAO,WAAW,sBAAsB,KAAK,CAAC;;;;;AAM3E,SAAgB,WACd,SAC4B;CAC5B,MAAM,SAAS,YAAY;CAC3B,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,aAAa,gBACjB,OAAO,QAAQ,QAAQ,CACpB,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC,CACpD,KAAK,CAAC,KAAK,YAAY;EACtB;EACA,eAAe,MAAM,MAAM;EAC3B,SAAS,MAAM,SAAS;EACxB,MACE,MAAM,SAAA,SACF,EAAE,GACF,sBAAsB,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAmB;EAClE,EAAE,CACN;CACD,MAAM,oBAAoB,cAClB,KAAK,MAAM,WAAW,EAC5B,CAAC,WAAW,CACb;CACD,MAAM,CAAC,YAAY,eAAe,IAAI,qBAAqB,OAAO,CAAC;CACnE,MAAM,GAAG,cAAc,SAAS,EAAE;AAElC,KAAI,SAAS,WAAW,OACtB,UAAS,cAAc,OAAO;AAGhC,uBAAsB,SAAS,SAAS,EAAE,CAAC,SAAS,CAAC;AAErD,iBAAgB;AACd,WAAS,WAAW,kBAAkB;AACtC,cAAY,UAAU,QAAQ,EAAE;AAChC,SAAO,SAAS,gBAAgB;AAC9B,eAAY,UAAU,QAAQ,EAAE;IAChC;IACD,CAAC,mBAAmB,SAAS,CAAC;CAEjC,MAAM,WAAW,SAAS,YAAY,kBAAkB;AAExD,QAAO,cAAc;AACnB,SAAO,OAAO,YACZ,kBAAkB,KAAK,UAAU,CAC/B,MAAM,KACN,aACE,SAAS,MAAM,QAAQ,cACvB,eACA,MAAM,QACP,CACF,CAAC,CACH;IACA;EAAC;EAAmB;EAAe;EAAS,CAAC;;;;;AAMlD,SAAgB,kBACd,WACA,MACA,SAGyD;AACzD,KACE,OAAO,QAAQ,oBAAoB,YACnC,QAAQ,mBAAmB,EAE3B,OAAM,IAAI,MACR,+DAA+D,OAC7D,QAAQ,gBACT,CAAC,GACH;CAGH,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,YAAY,SAAS;CAC3B,MAAM,iBAAiB,YAAY,EAAE,GAAI,QAAQ,EAAE;CACnD,MAAM,aAAa,gBAAgB;EACjC,eAAe,UAAU;EACzB,MAAM;EACN,iBAAiB,QAAQ;EACzB,SAAS;EACV,CAAC;CACF,MAAM,qBAAqB,qBAEtB;EACC;EACA,aAAa;EACb,OAAO,YACH,EAAE,GACF,CACE;GACE,KAAK;GACL,QAAQ;GACR,UAAU,QAAQ;GACnB,CACF;EACN,GACH;EAAC;EAAW,QAAQ;EAAiB;EAAW,CACjD;CACD,MAAM,CAAC,OAAO,YAAY,SACxB,mBACD;CAED,IAAI,eAAe;AACnB,KAAI,aAAa,eAAe,YAAY;AAC1C,iBAAe,oBAAoB;AACnC,WAAS,aAAa;;CAmBxB,MAAM,aAAa,WAhBC,cAAc;EAChC,MAAM,WAA8C,EAAE;AACtD,OAAK,MAAM,QAAQ,aAAa,MAC9B,UAAS,KAAK,OAAO;GACnB,OAAO;GACP,MAAM;IACJ,GAAI;IACJ,gBAAgB;KACd,QAAQ,KAAK;KACb,UAAU,KAAK;KAChB;IACF;GACF;AAEH,SAAO;IACN;EAAC,aAAa;EAAO;EAAgB;EAAU,CAAC,CACT;CAE1C,MAAM,UAAU,cAAc;EAC5B,MAAM,QAAiE,EAAE;EACzE,IAAI;AAEJ,OAAK,MAAM,QAAQ,aAAa,OAAO;GACrC,MAAM,YACJ,WAAW,KAAK;AAGlB,OAAI,CAAC,aAAa,UAAU,WAAW,UACrC;AAEF,OAAI,UAAU,WAAW,SAAS;AAChC,YAAQ,UAAU;AAClB;;AAEF,OAAI,UAAU,KACZ,OAAM,KAAK,UAAU,KAAK;;EAI9B,MAAM,UAAU,MAAM,SAAS,SAAS,KAAK,KAAK;EAClD,MAAM,iBAAiB,MAAM,GAAG,GAAG;EACnC,MAAM,mBAAmB,aAAa,MAAM,GAAG,GAAG,EAAE;EACpD,MAAM,qBAAqB,mBACtB,WAAW,oBAGZ,KAAA;EACJ,MAAM,YAAY,CAAC,aAAa,MAAM,WAAW,KAAK,CAAC;EACvD,MAAM,gBACJ,aAAa,MAAM,SAAS,MAAM,UACjC,CAAC,CAAC,sBAAsB,mBAAmB,WAAW,aAAa,MAAM,SAAS;EACrF,MAAM,UAAU,CAAC,CAAC,kBAAkB,CAAC,eAAe;EACpD,MAAM,SAAsC,QACxC,UACA,YACE,UACA,YACE,YACA,gBACE,gBACA,UACE,UACA;AAEZ,SAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA,QAAQ,gBAAgB,UAAU;GAClC;GACD;IACA;EAAC,aAAa;EAAO;EAAW;EAAW,CAAC;AAE/C,QAAO;EACL,GAAG;EACH;EACA,SAAS,WAAW,QAAQ,iBAAiB;AAC3C,OACE,aACA,QAAQ,SACR,QAAQ,iBACR,CAAC,QAAQ,WACT,CAAC,QAAQ,OAET;AAGF,aAAU,cAAc;IACtB,GAAG;IACH,aAAa,SAAS,cAAc;IACpC,OAAO,CACL,GAAG,SAAS,OACZ;KACE,KAAK,OAAO,SAAS,YAAY;KACjC,QAAQ,QAAQ;KAChB;KACD,CACF;IACF,EAAE;;EAEN;;AAGH,MAAM,eAAqC;CACzC,MAAM,KAAA;CACN,OAAO,KAAA;CACR;AAED,MAAM,YAAwC;CAC5C,sBAAsB,KAAA;CACtB,wBAAwB,KAAA;CACxB,uBAAuB,KAAA;CACxB;AAED,SAAS,qBACP,QACA,WACA,MACA,YAAY,OACkB;CAC9B,MAAM,UAAU,YAAY,OAAO,gBAAgB,QAAQ,EAAE,CAAC;CAC9D,MAAM,iBAAiB,cACd,YAAY,KAAA,IAAa,KAAK,MAAM,QAAQ,EACnD,CAAC,SAAS,UAAU,CACrB;CACD,MAAM,QAAQ,cAEV,YACI,YACC,OAAO,WACN,WACA,eACD,EACP;EAAC;EAAQ;EAAW;EAAgB;EAAU,CAC/C;AAED,uBACc;AACV,MAAI,CAAC,UACH,OAAM,WAAW;IAGrB,CAAC,WAAW,MAAM,CACnB;AAED,QAAO;;AAGT,SAAS,sBACP,MACO;AACP,QAAQ,KAAK,MAAM,EAAE;;AAGvB,SAAS,kBACP,OACwB;AACxB,QAAO;EACL,MAAM,MAAM,kBAAkB;EAC9B,OAAO,MAAM,iBAAiB;EAC/B;;AAGH,SAAS,oBACP,SAIwC;AACxC,QAAO,OAAO,YACZ,QAAQ,KAAK,UAAU,CAAC,MAAM,KAAK,MAAM,SAAS,CAAC,CACpD;;AAGH,SAAS,0BACP,OACsB;AACtB,QAAO,MAAM,kBAAkB,IAAI;;AAGrC,SAAS,aACP,UACA,eACA,WAC4B;AAC5B,KAAI,UACF,QAAO;EACL,MAAM,KAAA;EACN,OAAO,KAAA;EACP,QAAQ;EACR;EACA,WAAW;EACX,SAAS;EACT,SAAS;EACV;CAGH,MAAM,SACJ,SAAS,UAAU,KAAA,IACf,UACA,SAAS,SAAS,KAAA,IAChB,YACA;AAER,QAAO;EACL,MAAM,SAAS;EACf,OAAO,SAAS;EAChB;EACA;EACA,WAAW,WAAW;EACtB,SAAS,WAAW;EACpB,SAAS,WAAW;EACrB;;AAGH,SAAS,gBAAgB,OAAwB;AAC/C,QAAO,KAAK,UAAU,UAAU,MAAM,CAAC;;AAGzC,SAAS,UAAU,OAAyB;AAC1C,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,UAAU;AAE7B,KAAI,SAAS,OAAO,UAAU,SAC5B,QAAO,OAAO,YACZ,OAAO,QAAQ,MAAiC,CAC7C,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC,CACpD,KAAK,CAAC,KAAK,YAAY,CAAC,KAAK,UAAU,OAAO,CAAC,CAAC,CACpD;AAEH,QAAO;;AAGT,IAAM,uBAAN,MAA2B;CACzB;CACA,4BAA6B,IAAI,KAAiB;CAClD,0BAA2B,IAAI,KAAkC;CAEjE,YAAY,QAAuB;AACjC,OAAK,SAAS;;CAGhB,cAAc,QAA6B;AACzC,OAAK,SAAS;AACb,OAAmC,SAAS;;CAG/C,WAAW,SAAuC;EAChD,MAAM,aAAa,IAAI,IAAI,QAAQ,KAAK,UAAU,MAAM,IAAI,CAAC;AAE7D,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,aAAa,GAAG,MAAM,cAAc,GAAG,gBAAgB,MAAM,KAAK,CAAC,GAAG,OAC1E,MAAM,QACP;GACD,MAAM,UAAU,KAAK,QAAQ,IAAI,MAAM,IAAI;AAC3C,OAAI,SAAS,eAAe,WAC1B;AAGF,YAAS,aAAa;AACtB,YAAS,OAAO,WAAW;AAE3B,OAAI,MAAM,SAAS;AACjB,SAAK,QAAQ,IAAI,MAAM,KAAK;KAC1B;KACA,UAAU;KACV,mBAAmB,KAAA;KACpB,CAAC;AACF;;GAGF,MAAM,QAAQ,KAAK,OAAO,WACxB;IAAE,MAAM;IAAS,MAAM,MAAM;IAAe,EAC5C,MAAM,KACP;GACD,MAAM,SAA8B;IAClC;IACA,UAAU,kBAAkB,MAAM;IAClC,mBAAmB,KAAA;IACnB;IACD;AACD,UAAO,cAAc,MAAM,eAAe;AACxC,WAAO,WAAW,kBAAkB,MAAM;AAC1C,SAAK,QAAQ;KACb;AACF,QAAK,QAAQ,IAAI,MAAM,KAAK,OAAO;;AAGrC,OAAK,MAAM,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,EAAE;AAClD,OAAI,WAAW,IAAI,IAAI,CACrB;AAEF,UAAO,aAAa;AACpB,UAAO,OAAO,WAAW;AACzB,QAAK,QAAQ,OAAO,IAAI;;;CAI5B,YAAY,SAAyE;AACnF,SAAO,oBACL,QAAQ,KAAK,WAAW;GACtB,KAAK,MAAM;GACX,UAAU,KAAK,QAAQ,IAAI,MAAM,IAAI,EAAE,YAAY;GACpD,EAAE,CACJ;;CAGH,UAAU,UAAkC;AAC1C,OAAK,UAAU,IAAI,SAAS;AAC5B,eAAa;AACX,QAAK,UAAU,OAAO,SAAS;;;CAInC,UAAgB;AACd,OAAK,MAAM,UAAU,KAAK,QAAQ,QAAQ,EAAE;AAC1C,UAAO,aAAa;AACpB,UAAO,OAAO,WAAW;;AAE3B,OAAK,QAAQ,OAAO;;CAGtB,SAAuB;AACrB,OAAK,MAAM,YAAY,KAAK,UAC1B,WAAU"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import {\n createContext,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n useState\n} from \"react\";\nimport type {\n FunctionArgs,\n FunctionReference,\n FunctionResult,\n PaginationOptions,\n PaginationResult,\n SyncoreClient,\n SyncorePaginatedQueryStatus,\n SyncoreQueryState,\n SyncoreRuntimeStatus,\n SyncoreWatch,\n UsePaginatedQueryResult\n} from \"@syncore/core\";\n\ntype ManagedSyncoreWatch<TResult> = SyncoreWatch<TResult> & {\n dispose?: () => void;\n};\n\ntype OptionalArgsTuple<TArgs> =\n Record<never, never> extends TArgs ? [args?: TArgs] : [args: TArgs];\n\ntype QueryRequestInput<\n TReference extends FunctionReference<\"query\"> = FunctionReference<\"query\">\n> = Record<never, never> extends FunctionArgs<TReference>\n ? {\n query: TReference;\n args?: FunctionArgs<TReference> | Skip;\n }\n : {\n query: TReference;\n args: FunctionArgs<TReference> | Skip;\n };\n\ntype QueriesRequestInput = Record<string, QueryRequestInput>;\n\ntype QueryStateForEntry<TEntry> = TEntry extends QueryRequestInput<\n infer TReference\n>\n ? SyncoreQueryState<FunctionResult<TReference>>\n : never;\n\nexport type UseQueriesResult<TEntries extends QueriesRequestInput> = {\n [TKey in keyof TEntries]: QueryStateForEntry<TEntries[TKey]>;\n};\n\ntype PaginatedQueryReference = FunctionReference<\n \"query\",\n Record<string, unknown>,\n PaginationResult<unknown>\n>;\n\ntype PaginatedQueryArgs<TReference extends FunctionReference<\"query\">> =\n FunctionArgs<TReference> extends { paginationOpts: PaginationOptions }\n ? Omit<FunctionArgs<TReference>, \"paginationOpts\">\n : never;\n\ntype PaginatedQueryItem<TReference extends FunctionReference<\"query\">> =\n FunctionResult<TReference> extends PaginationResult<infer TItem>\n ? TItem\n : never;\n\ntype QuerySnapshot<TResult> = {\n data: TResult | undefined;\n error: Error | undefined;\n};\n\ntype NormalizedQueryEntry = {\n key: string;\n referenceName: string;\n args: Record<string, unknown>;\n skipped: boolean;\n};\n\ntype PaginatedQueryInternalState = {\n requestKey: string;\n nextPageKey: number;\n pages: Array<{\n key: string;\n cursor: string | null;\n numItems: number;\n }>;\n};\n\ntype QueryObserverRecord = {\n requestKey: string;\n snapshot: QuerySnapshot<unknown>;\n unsubscribe: () => void;\n watch?: ManagedSyncoreWatch<unknown>;\n};\n\n/**\n * Pass `skip` as the `args` argument to any Syncore React hook to suppress\n * that subscription entirely.\n *\n * Useful when the query arguments depend on state that is not yet available\n * (e.g. a selected item ID) — instead of conditionally calling the hook\n * (which violates the Rules of Hooks), pass `skip` to deactivate it:\n *\n * ```tsx\n * const task = useQuery(api.tasks.get, selectedId ? { id: selectedId } : skip);\n * // task is `undefined` while selectedId is null/undefined\n * ```\n *\n * Skipped queries return `undefined` for `data`, `\"skipped\"` for `status`,\n * and `false` for `isLoading`.\n */\nexport const skip = \"skip\" as const;\ntype Skip = typeof skip;\n\nconst defaultRuntimeStatus: SyncoreRuntimeStatus = {\n kind: \"starting\",\n reason: \"booting\"\n};\n\nconst SyncoreContext = createContext<SyncoreClient | null>(null);\n\n/**\n * Provides a Syncore client to all React descendants via context.\n *\n * Wrap your app (or any subtree that uses Syncore hooks) with\n * `SyncoreProvider`. All `useQuery`, `useMutation`, `useAction`, and\n * `useQueries` calls inside the tree will automatically use the client you\n * supply.\n *\n * ```tsx\n * // For a browser worker setup\n * const client = createBrowserWorkerClient();\n *\n * function App() {\n * return (\n * <SyncoreProvider client={client}>\n * <TaskList />\n * </SyncoreProvider>\n * );\n * }\n * ```\n *\n * For Next.js apps use `SyncoreNextProvider` which also handles service worker\n * and worker URL configuration.\n */\nexport function SyncoreProvider({\n client,\n children\n}: {\n client: SyncoreClient;\n children: ReactNode;\n}) {\n return (\n <SyncoreContext.Provider value={client}>{children}</SyncoreContext.Provider>\n );\n}\n\n/**\n * Returns the active `SyncoreClient` from the nearest {@link SyncoreProvider}\n * in the React tree.\n *\n * Throws if called outside of a `SyncoreProvider`. Prefer the higher-level\n * hooks (`useQuery`, `useMutation`, etc.) for common operations — use\n * `useSyncore` only when you need direct access to the client object.\n *\n * ```ts\n * const client = useSyncore();\n * const tasks = await client.query(api.tasks.list);\n * ```\n */\nexport function useSyncore(): SyncoreClient {\n const client = useContext(SyncoreContext);\n if (!client) {\n throw new Error(\"SyncoreProvider is missing from the React tree.\");\n }\n return client;\n}\n\n/**\n * Subscribe to the runtime’s lifecycle status.\n *\n * Returns a {@link SyncoreRuntimeStatus} that updates whenever the underlying\n * runtime changes state (e.g. starting, ready, error). Use it to gate your UI\n * on the runtime being ready or to display an error boundary:\n *\n * ```tsx\n * function TaskList() {\n * const status = useSyncoreStatus();\n * if (status.kind === \"starting\") return <Spinner />;\n * if (status.kind === \"error\") return <ErrorScreen error={status.error} />;\n * return <Tasks />;\n * }\n * ```\n *\n * Most components do not need this — `useQuery` already incorporates runtime\n * status into the `SyncoreQueryState.runtimeStatus` field.\n */\nexport function useSyncoreStatus(): SyncoreRuntimeStatus {\n const client = useSyncore();\n const watch = useMemo(\n () => client.watchRuntimeStatus() as ManagedSyncoreWatch<SyncoreRuntimeStatus>,\n [client]\n );\n const [status, setStatus] = useState<SyncoreRuntimeStatus>(() =>\n readRuntimeStatusSnapshot(watch)\n );\n\n useEffect(() => {\n const sync = () => {\n setStatus(readRuntimeStatusSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch]);\n\n useEffect(\n () => () => {\n watch.dispose?.();\n },\n [watch]\n );\n\n return status;\n}\n\n/**\n * Subscribe to a reactive Syncore query and return the current data.\n *\n * The component re-renders automatically whenever the query result changes.\n * If the query throws, `useQuery` re-throws the error so a React error\n * boundary can catch it — use {@link useQueryState} if you need to handle\n * errors inline.\n *\n * ```tsx\n * // Basic usage\n * const tasks = useQuery(api.tasks.list);\n *\n * // With arguments\n * const task = useQuery(api.tasks.get, { id: taskId });\n *\n * // Conditionally skip when arguments are not yet available\n * const task = useQuery(api.tasks.get, taskId ? { id: taskId } : skip);\n * ```\n *\n * @param reference - A typed function reference (from the generated `api` object).\n * @param args - The query’s arguments, or `skip` to suppress the subscription.\n * @returns The current query result, or `undefined` while loading.\n */\nexport function useQuery<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): TResult | undefined {\n const state = useQueryState(reference, ...(args as OptionalArgsTuple<TArgs> | [Skip]));\n if (state.error) {\n throw state.error;\n }\n return state.data;\n}\n\n/**\n * Subscribe to a reactive Syncore query and return the full\n * {@link SyncoreQueryState} including loading, error, and runtime status.\n *\n * Use this instead of {@link useQuery} when you need to:\n * - Differentiate between `undefined` data and an error.\n * - React to `isLoading` / `isError` without relying on error boundaries.\n * - Inspect `runtimeStatus` for the underlying runtime’s health.\n *\n * ```tsx\n * const { data, isLoading, isError, error } = useQueryState(api.tasks.list);\n *\n * if (isLoading) return <Spinner />;\n * if (isError) return <ErrorBanner message={error.message} />;\n * return <TaskList tasks={data} />;\n * ```\n */\nexport function useQueryState<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): SyncoreQueryState<TResult> {\n const isSkipped = args[0] === skip;\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const watch = useManagedQueryWatch(\n client,\n reference,\n isSkipped\n ? undefined\n : normalizeOptionalArgs(args as OptionalArgsTuple<TArgs>),\n isSkipped\n );\n const [snapshot, setSnapshot] = useState<QuerySnapshot<TResult>>(() =>\n isSkipped ? noOpSnapshot : readWatchSnapshot(watch)\n );\n\n useEffect(() => {\n if (isSkipped) {\n setSnapshot(noOpSnapshot);\n return;\n }\n const sync = () => {\n setSnapshot(readWatchSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch, isSkipped]);\n\n return toQueryState(snapshot, runtimeStatus, isSkipped);\n}\n\n/**\n * Returns a stable callback for executing a Syncore mutation.\n *\n * The returned function is type-safe: its parameter types are inferred from\n * the mutation definition and remain stable across re-renders (no need to\n * wrap in `useCallback`).\n *\n * ```tsx\n * const createTask = useMutation(api.tasks.create);\n *\n * return (\n * <button onClick={() => createTask({ title: \"New task\" })}>\n * Add task\n * </button>\n * );\n * ```\n *\n * @param reference - A typed mutation reference from the generated `api` object.\n * @returns A function that, when called, executes the mutation and returns a\n * promise that resolves to the mutation’s return value.\n */\nexport function useMutation<TArgs, TResult>(\n reference: FunctionReference<\"mutation\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.mutation(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Returns a stable callback for executing a Syncore action.\n *\n * Identical to {@link useMutation} but for actions. Use this when the work you\n * need to do cannot run inside a transaction (external API calls, long-running\n * tasks, etc.).\n *\n * ```tsx\n * const importTasks = useAction(api.tasks.importFromCsv);\n *\n * return (\n * <button onClick={() => importTasks({ url: csvUrl })}>\n * Import\n * </button>\n * );\n * ```\n *\n * @param reference - A typed action reference from the generated `api` object.\n * @returns A function that, when called, executes the action and returns a\n * promise that resolves to the action’s return value.\n */\nexport function useAction<TArgs, TResult>(\n reference: FunctionReference<\"action\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.action(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Subscribe to multiple Syncore queries simultaneously and receive per-entry\n * state objects in a single hook call.\n *\n * More efficient than calling `useQuery` in a loop when the set of queries is\n * known at component render time. The hook maintains only one subscription per\n * unique `(reference, args)` combination even if entries are duplicated.\n *\n * ```tsx\n * const { header, sidebar } = useQueries({\n * header: { query: api.layout.header },\n * sidebar: { query: api.layout.sidebar, args: { userId } },\n * });\n *\n * if (header.isLoading || sidebar.isLoading) return <Spinner />;\n * ```\n *\n * @param entries - A record of named query requests. Each entry can include\n * `args: skip` to suppress that specific subscription.\n * @returns A record with the same keys, each holding a {@link SyncoreQueryState}.\n */\nexport function useQueries<TEntries extends QueriesRequestInput>(\n entries: TEntries\n): UseQueriesResult<TEntries> {\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const entriesKey = stableStringify(\n Object.entries(entries)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => ({\n key,\n referenceName: entry.query.name,\n skipped: entry.args === skip,\n args:\n entry.args === skip\n ? {}\n : normalizeOptionalArgs([entry.args ?? {}] as [] | [unknown])\n }))\n );\n const normalizedEntries = useMemo(\n () => JSON.parse(entriesKey) as NormalizedQueryEntry[],\n [entriesKey]\n );\n const [observer] = useState(() => new ReactQueriesObserver(client));\n const [, setVersion] = useState(0);\n\n if (observer.client !== client) {\n observer.replaceClient(client);\n }\n\n useEffect(() => () => observer.destroy(), [observer]);\n\n useEffect(() => {\n observer.setEntries(normalizedEntries);\n setVersion((value) => value + 1);\n return observer.subscribe(() => {\n setVersion((value) => value + 1);\n });\n }, [normalizedEntries, observer]);\n\n const snapshot = observer.getSnapshot(normalizedEntries);\n\n return useMemo(() => {\n return Object.fromEntries(\n normalizedEntries.map((entry) => [\n entry.key,\n toQueryState(\n snapshot[entry.key] ?? noOpSnapshot,\n runtimeStatus,\n entry.skipped\n )\n ])\n ) as UseQueriesResult<TEntries>;\n }, [normalizedEntries, runtimeStatus, snapshot]);\n}\n\n/**\n * Subscribe to a paginated Syncore query, incrementally loading more pages.\n *\n * The query must accept a `paginationOpts` argument and return a\n * `PaginationResult`. The hook manages cursors automatically — call the\n * returned `loadMore` function to append the next page to the results.\n *\n * ```tsx\n * const { results, status, loadMore, hasMore } = usePaginatedQuery(\n * api.tasks.list,\n * { projectId },\n * { initialNumItems: 20 },\n * );\n *\n * return (\n * <>\n * {results.map((t) => <TaskRow key={t._id} task={t} />)}\n * {hasMore && (\n * <button\n * onClick={() => loadMore(20)}\n * disabled={status === \"loadingMore\"}\n * >\n * Load more\n * </button>\n * )}\n * </>\n * );\n * ```\n *\n * Pass `skip` as `args` to suppress the subscription until arguments are\n * ready.\n *\n * @param reference - A typed query reference whose handler calls\n * `ctx.db.query(…).paginate(paginationOpts)`.\n * @param args - Arguments for the query (excluding\n * `paginationOpts`, which is managed internally), or `skip`.\n * @param options.initialNumItems - Number of items to load on the first page.\n * @returns A {@link UsePaginatedQueryResult} with the accumulated results and\n * a `loadMore` callback.\n */\nexport function usePaginatedQuery<TReference extends PaginatedQueryReference>(\n reference: TReference,\n args: PaginatedQueryArgs<TReference> | Skip,\n options: {\n initialNumItems: number;\n }\n): UsePaginatedQueryResult<PaginatedQueryItem<TReference>> {\n if (\n typeof options.initialNumItems !== \"number\" ||\n options.initialNumItems <= 0\n ) {\n throw new Error(\n `options.initialNumItems must be a positive number. Received ${String(\n options.initialNumItems\n )}.`\n );\n }\n\n const runtimeStatus = useSyncoreStatus();\n const isSkipped = args === skip;\n const normalizedArgs = isSkipped ? {} : (args ?? {});\n const requestKey = stableStringify({\n referenceName: reference.name,\n args: normalizedArgs,\n initialNumItems: options.initialNumItems,\n skipped: isSkipped\n });\n const createInitialState = useMemo(\n () => () =>\n ({\n requestKey,\n nextPageKey: 1,\n pages: isSkipped\n ? []\n : [\n {\n key: \"0\",\n cursor: null,\n numItems: options.initialNumItems\n }\n ]\n }) satisfies PaginatedQueryInternalState,\n [isSkipped, options.initialNumItems, requestKey]\n );\n const [state, setState] = useState<PaginatedQueryInternalState>(\n createInitialState\n );\n\n let currentState = state;\n if (currentState.requestKey !== requestKey) {\n currentState = createInitialState();\n setState(currentState);\n }\n\n const pageQueries = useMemo(() => {\n const requests: Record<string, QueryRequestInput> = {};\n for (const page of currentState.pages) {\n requests[page.key] = {\n query: reference,\n args: {\n ...(normalizedArgs as Record<string, unknown>),\n paginationOpts: {\n cursor: page.cursor,\n numItems: page.numItems\n }\n }\n };\n }\n return requests;\n }, [currentState.pages, normalizedArgs, reference]);\n const pageStates = useQueries(pageQueries);\n\n const derived = useMemo(() => {\n const pages: Array<PaginationResult<PaginatedQueryItem<TReference>>> = [];\n let error: Error | undefined;\n\n for (const page of currentState.pages) {\n const pageState =\n pageStates[page.key as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined;\n if (!pageState || pageState.status === \"loading\") {\n break;\n }\n if (pageState.status === \"error\") {\n error = pageState.error;\n break;\n }\n if (pageState.data) {\n pages.push(pageState.data);\n }\n }\n\n const results = pages.flatMap((page) => page.page);\n const lastLoadedPage = pages.at(-1);\n const lastRequestedKey = currentState.pages.at(-1)?.key;\n const lastRequestedState = lastRequestedKey\n ? (pageStates[lastRequestedKey as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined)\n : undefined;\n const isLoading = !isSkipped && pages.length === 0 && !error;\n const isLoadingMore =\n currentState.pages.length > pages.length ||\n (!!lastRequestedState && lastRequestedState.status === \"loading\" && pages.length > 0);\n const hasMore = !!lastLoadedPage && !lastLoadedPage.isDone;\n const status: SyncorePaginatedQueryStatus = error\n ? \"error\"\n : isSkipped\n ? \"ready\"\n : isLoading\n ? \"loading\"\n : isLoadingMore\n ? \"loadingMore\"\n : hasMore\n ? \"ready\"\n : \"exhausted\";\n\n return {\n pages,\n results,\n error,\n isLoading,\n isLoadingMore,\n hasMore,\n cursor: lastLoadedPage?.cursor ?? null,\n status\n };\n }, [currentState.pages, isSkipped, pageStates]);\n\n return {\n ...derived,\n runtimeStatus,\n loadMore(numItems = options.initialNumItems) {\n if (\n isSkipped ||\n derived.error ||\n derived.isLoadingMore ||\n !derived.hasMore ||\n !derived.cursor\n ) {\n return;\n }\n\n setState((previous) => ({\n ...previous,\n nextPageKey: previous.nextPageKey + 1,\n pages: [\n ...previous.pages,\n {\n key: String(previous.nextPageKey),\n cursor: derived.cursor,\n numItems\n }\n ]\n }));\n }\n };\n}\n\nconst noOpSnapshot: QuerySnapshot<never> = {\n data: undefined,\n error: undefined\n};\n\nconst noOpWatch: ManagedSyncoreWatch<never> = {\n onUpdate: () => () => undefined,\n localQueryResult: () => undefined,\n localQueryError: () => undefined\n};\n\nfunction useManagedQueryWatch<TArgs, TResult>(\n client: SyncoreClient,\n reference: FunctionReference<\"query\", TArgs, TResult>,\n args?: TArgs,\n isSkipped = false\n): ManagedSyncoreWatch<TResult> {\n const argsKey = isSkipped ? skip : stableStringify(args ?? {});\n const [watch, setWatch] = useState<ManagedSyncoreWatch<TResult>>(\n () => noOpWatch\n );\n\n useEffect(() => {\n if (isSkipped) {\n setWatch(noOpWatch);\n return;\n }\n\n const nextWatch = client.watchQuery(\n reference,\n JSON.parse(argsKey) as TArgs\n ) as ManagedSyncoreWatch<TResult>;\n setWatch(nextWatch);\n\n return () => {\n nextWatch.dispose?.();\n };\n }, [argsKey, client, isSkipped, reference]);\n\n return watch;\n}\n\nfunction normalizeOptionalArgs<TArgs>(\n args: [] | [TArgs] | readonly unknown[]\n): TArgs {\n return (args[0] ?? {}) as TArgs;\n}\n\nfunction readWatchSnapshot<TResult>(\n watch: SyncoreWatch<TResult>\n): QuerySnapshot<TResult> {\n return {\n data: watch.localQueryResult(),\n error: watch.localQueryError()\n };\n}\n\nfunction readQueriesSnapshot(\n records: Array<{\n key: string;\n snapshot: QuerySnapshot<unknown>;\n }>\n): Record<string, QuerySnapshot<unknown>> {\n return Object.fromEntries(\n records.map((entry) => [entry.key, entry.snapshot])\n );\n}\n\nfunction readRuntimeStatusSnapshot(\n watch: SyncoreWatch<SyncoreRuntimeStatus>\n): SyncoreRuntimeStatus {\n return watch.localQueryResult() ?? defaultRuntimeStatus;\n}\n\nfunction toQueryState<TResult>(\n snapshot: QuerySnapshot<TResult>,\n runtimeStatus: SyncoreRuntimeStatus,\n isSkipped: boolean\n): SyncoreQueryState<TResult> {\n if (isSkipped) {\n return {\n data: undefined,\n error: undefined,\n status: \"skipped\",\n runtimeStatus,\n isLoading: false,\n isError: false,\n isReady: false\n };\n }\n\n const status =\n snapshot.error !== undefined\n ? \"error\"\n : snapshot.data === undefined\n ? \"loading\"\n : \"success\";\n\n return {\n data: snapshot.data,\n error: snapshot.error,\n status,\n runtimeStatus,\n isLoading: status === \"loading\",\n isError: status === \"error\",\n isReady: status === \"success\"\n };\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortValue);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, sortValue(nested)])\n );\n }\n return value;\n}\n\nclass ReactQueriesObserver {\n readonly client: SyncoreClient;\n private readonly listeners = new Set<() => void>();\n private readonly records = new Map<string, QueryObserverRecord>();\n\n constructor(client: SyncoreClient) {\n this.client = client;\n }\n\n replaceClient(client: SyncoreClient): void {\n this.destroy();\n (this as { client: SyncoreClient }).client = client;\n }\n\n setEntries(entries: NormalizedQueryEntry[]): void {\n const activeKeys = new Set(entries.map((entry) => entry.key));\n\n for (const entry of entries) {\n const requestKey = `${entry.referenceName}:${stableStringify(entry.args)}:${String(\n entry.skipped\n )}`;\n const current = this.records.get(entry.key);\n if (current?.requestKey === requestKey) {\n continue;\n }\n\n current?.unsubscribe();\n current?.watch?.dispose?.();\n\n if (entry.skipped) {\n this.records.set(entry.key, {\n requestKey,\n snapshot: noOpSnapshot,\n unsubscribe: () => undefined\n });\n continue;\n }\n\n const watch = this.client.watchQuery(\n { kind: \"query\", name: entry.referenceName },\n entry.args\n ) as ManagedSyncoreWatch<unknown>;\n const record: QueryObserverRecord = {\n requestKey,\n snapshot: readWatchSnapshot(watch),\n unsubscribe: () => undefined,\n watch\n };\n record.unsubscribe = watch.onUpdate(() => {\n record.snapshot = readWatchSnapshot(watch);\n this.notify();\n });\n this.records.set(entry.key, record);\n }\n\n for (const [key, record] of this.records.entries()) {\n if (activeKeys.has(key)) {\n continue;\n }\n record.unsubscribe();\n record.watch?.dispose?.();\n this.records.delete(key);\n }\n }\n\n getSnapshot(entries: NormalizedQueryEntry[]): Record<string, QuerySnapshot<unknown>> {\n return readQueriesSnapshot(\n entries.map((entry) => ({\n key: entry.key,\n snapshot: this.records.get(entry.key)?.snapshot ?? noOpSnapshot\n }))\n );\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n destroy(): void {\n for (const record of this.records.values()) {\n record.unsubscribe();\n record.watch?.dispose?.();\n }\n this.records.clear();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -16,6 +16,14 @@ interface TableDefinitionOptions {
|
|
|
16
16
|
componentPath?: string;
|
|
17
17
|
componentName?: string;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The built-in system fields automatically added to every Syncore document.
|
|
21
|
+
*
|
|
22
|
+
* You never supply these when inserting; Syncore sets them for you.
|
|
23
|
+
* - `_id` — A unique string identifier for the document. Use `s.id(tableName)` to
|
|
24
|
+
* type foreign-key fields that reference this value.
|
|
25
|
+
* - `_creationTime` — Unix timestamp (milliseconds) when the document was inserted.
|
|
26
|
+
*/
|
|
19
27
|
interface TableDocumentSystemFields {
|
|
20
28
|
_id: string;
|
|
21
29
|
_creationTime: number;
|
|
@@ -25,6 +33,18 @@ type GenericTableSearchIndexes = Record<string, {
|
|
|
25
33
|
searchField: string;
|
|
26
34
|
filterFields: string;
|
|
27
35
|
}>;
|
|
36
|
+
/**
|
|
37
|
+
* A typed table definition that describes the shape, indexes, and search
|
|
38
|
+
* indexes for a single Syncore table.
|
|
39
|
+
*
|
|
40
|
+
* You create `TableDefinition` instances with {@link defineTable} and attach
|
|
41
|
+
* indexes with the fluent `.index()` and `.searchIndex()` methods. The type
|
|
42
|
+
* parameters track the validator shape, registered indexes, and search indexes
|
|
43
|
+
* so that {@link QueryBuilder} can enforce correct field names at compile time.
|
|
44
|
+
*
|
|
45
|
+
* You almost never reference `TableDefinition` directly — it is the value
|
|
46
|
+
* produced by `defineTable` and consumed internally by `defineSchema`.
|
|
47
|
+
*/
|
|
28
48
|
declare class TableDefinition<TValidator extends Validator<Record<string, unknown>, Record<string, unknown>, string>, TIndexes = Record<never, never>, TSearchIndexes = Record<never, never>> {
|
|
29
49
|
readonly validator: TValidator;
|
|
30
50
|
readonly indexes: IndexDefinition[];
|
|
@@ -36,7 +56,55 @@ declare class TableDefinition<TValidator extends Validator<Record<string, unknow
|
|
|
36
56
|
readonly indexesByName: TIndexes;
|
|
37
57
|
readonly searchIndexesByName: TSearchIndexes;
|
|
38
58
|
constructor(validator: TValidator, options?: TableDefinitionOptions);
|
|
59
|
+
/**
|
|
60
|
+
* Register a named index on one or more fields of this table.
|
|
61
|
+
*
|
|
62
|
+
* Indexes allow efficient range queries via `ctx.db.query(table).withIndex()`.
|
|
63
|
+
* The first field listed is the primary sort key; additional fields refine
|
|
64
|
+
* within equal values of the first key.
|
|
65
|
+
*
|
|
66
|
+
* **Rules**
|
|
67
|
+
* - An index must cover at least one field.
|
|
68
|
+
* - Field names must be top-level paths that exist in the table validator.
|
|
69
|
+
* - Index names must be unique within the table.
|
|
70
|
+
* - Index definitions are immutable after creation — changing the fields of
|
|
71
|
+
* an existing index requires a manual migration.
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* defineTable({ status: s.string(), createdAt: s.number(), ownerId: s.string() })
|
|
75
|
+
* .index("by_owner_and_status", ["ownerId", "status"])
|
|
76
|
+
* .index("by_created", ["createdAt"])
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
39
79
|
index<const TIndexName extends string, TFirstField extends FieldPaths<TValidator>, TRestFields extends FieldPaths<TValidator>[]>(name: TIndexName, fields: [TFirstField, ...TRestFields]): TableDefinition<TValidator, Expand<TIndexes & Record<TIndexName, readonly [TFirstField, ...TRestFields]>>, TSearchIndexes>;
|
|
80
|
+
/**
|
|
81
|
+
* Register a named full-text search index on this table.
|
|
82
|
+
*
|
|
83
|
+
* Search indexes power `ctx.db.query(table).withSearchIndex()` queries. Each
|
|
84
|
+
* search index specifies:
|
|
85
|
+
* - A single `searchField` that is tokenised and indexed for full-text
|
|
86
|
+
* matching.
|
|
87
|
+
* - Zero or more `filterFields` that can be used to narrow results with
|
|
88
|
+
* equality conditions.
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* defineTable({ title: s.string(), status: s.string(), ownerId: s.string() })
|
|
92
|
+
* .searchIndex("search_title", {
|
|
93
|
+
* searchField: "title",
|
|
94
|
+
* filterFields: ["status", "ownerId"],
|
|
95
|
+
* })
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* In a query handler:
|
|
99
|
+
* ```ts
|
|
100
|
+
* const results = await ctx.db
|
|
101
|
+
* .query("tasks")
|
|
102
|
+
* .withSearchIndex("search_title", (q) =>
|
|
103
|
+
* q.search("title", searchText).eq("status", "todo")
|
|
104
|
+
* )
|
|
105
|
+
* .collect();
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
40
108
|
searchIndex<const TIndexName extends string, TSearchField extends FieldPaths<TValidator>, TFilterField extends FieldPaths<TValidator> = never>(name: TIndexName, config: {
|
|
41
109
|
searchField: TSearchField;
|
|
42
110
|
filterFields?: TFilterField[];
|
|
@@ -66,17 +134,78 @@ type TableFieldDefinitionSummary = {
|
|
|
66
134
|
storage: ReturnType<AnyTableDefinition["describe"]>;
|
|
67
135
|
optional: boolean;
|
|
68
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* Define a Syncore table by specifying its field validators.
|
|
139
|
+
*
|
|
140
|
+
* `defineTable` is the building block of your data model. Pass a validator map
|
|
141
|
+
* (keys are field names, values are `s.*` validators) or a single `s.object()`
|
|
142
|
+
* validator. Chain `.index()` and `.searchIndex()` to register query indexes.
|
|
143
|
+
*
|
|
144
|
+
* The system fields `_id` and `_creationTime` are added automatically and
|
|
145
|
+
* should not be included in the shape.
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { defineTable, s } from "syncorejs";
|
|
149
|
+
*
|
|
150
|
+
* const tasks = defineTable({
|
|
151
|
+
* title: s.string(),
|
|
152
|
+
* status: s.enum(["todo", "done"] as const),
|
|
153
|
+
* projectId: s.nullable(s.id("projects")),
|
|
154
|
+
* dueAt: s.optional(s.number()),
|
|
155
|
+
* })
|
|
156
|
+
* .index("by_project", ["projectId"])
|
|
157
|
+
* .index("by_status", ["status"])
|
|
158
|
+
* .searchIndex("search_title", { searchField: "title", filterFields: ["status"] });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
69
161
|
declare function defineTable<const TShape extends ObjectValidatorShape>(validator: TShape): TableDefinition<ObjectValidator<TShape>>;
|
|
70
162
|
declare function defineTable<TValidator extends Validator<Record<string, unknown>, Record<string, unknown>, string>>(validator: TValidator): TableDefinition<TValidator>;
|
|
71
163
|
interface SyncoreSchemaDefinition {
|
|
72
164
|
[tableName: string]: AnyTableDefinition;
|
|
73
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* The typed data model produced by {@link defineSchema}.
|
|
168
|
+
*
|
|
169
|
+
* `SyncoreSchema` holds the table map and is the value you export from
|
|
170
|
+
* `syncore/schema.ts`. The runtime, context types, and codegen all reference
|
|
171
|
+
* this type to ensure end-to-end type safety between your schema definition
|
|
172
|
+
* and your function handlers.
|
|
173
|
+
*/
|
|
74
174
|
declare class SyncoreSchema<TTables> {
|
|
75
175
|
readonly tables: TTables;
|
|
76
176
|
constructor(tables: TTables);
|
|
77
177
|
getTable<TTableName extends Extract<keyof TTables, string>>(tableName: TTableName): TTables[TTableName];
|
|
78
178
|
tableNames(): Array<Extract<keyof TTables, string>>;
|
|
79
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Define the complete data model for a Syncore app.
|
|
182
|
+
*
|
|
183
|
+
* Pass an object whose keys are table names and values are `defineTable()`
|
|
184
|
+
* results. The resulting schema is passed to the runtime options and to the
|
|
185
|
+
* code generator.
|
|
186
|
+
*
|
|
187
|
+
* **Typical file: `syncore/schema.ts`**
|
|
188
|
+
*
|
|
189
|
+
* ```ts
|
|
190
|
+
* import { defineSchema, defineTable, s } from "syncorejs";
|
|
191
|
+
*
|
|
192
|
+
* export default defineSchema({
|
|
193
|
+
* tasks: defineTable({
|
|
194
|
+
* title: s.string(),
|
|
195
|
+
* status: s.enum(["todo", "done"] as const),
|
|
196
|
+
* projectId: s.nullable(s.id("projects")),
|
|
197
|
+
* })
|
|
198
|
+
* .index("by_project", ["projectId"]),
|
|
199
|
+
*
|
|
200
|
+
* projects: defineTable({
|
|
201
|
+
* name: s.string(),
|
|
202
|
+
* archivedAt: s.optional(s.number()),
|
|
203
|
+
* }),
|
|
204
|
+
* });
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @param tables - A map of table names to their `TableDefinition` values.
|
|
208
|
+
*/
|
|
80
209
|
declare function defineSchema<const TTables extends SyncoreSchemaDefinition>(tables: TTables): SyncoreSchema<TTables>;
|
|
81
210
|
//#endregion
|
|
82
211
|
export { AnyTableDefinition, GenericTableIndexes, GenericTableSearchIndexes, IndexDefinition, InferDocument, InferTableInput, SearchIndexDefinition, SyncoreSchema, SyncoreSchemaDefinition, TableDefinition, TableDefinitionOptions, TableDocumentSystemFields, TableFieldDefinitionSummary, TableFieldPaths, TableIndexFields, TableIndexNames, TableIndexes, TableSearchIndexConfig, TableSearchIndexNames, TableSearchIndexes, defineSchema, defineTable };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","names":[],"sources":["../src/definition.ts"],"mappings":";;;KAaK,MAAA,uBAA6B,CAAA,GAAI,CAAA,CAAE,IAAA;AAAA,UAEvB,eAAA;EACf,IAAA;EACA,
|
|
1
|
+
{"version":3,"file":"definition.d.ts","names":[],"sources":["../src/definition.ts"],"mappings":"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"}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { describeValidator, deserializeValue, ensureObjectValidator, serializeValue } from "./validators.js";
|
|
2
2
|
//#region src/definition.ts
|
|
3
|
+
/**
|
|
4
|
+
* A typed table definition that describes the shape, indexes, and search
|
|
5
|
+
* indexes for a single Syncore table.
|
|
6
|
+
*
|
|
7
|
+
* You create `TableDefinition` instances with {@link defineTable} and attach
|
|
8
|
+
* indexes with the fluent `.index()` and `.searchIndex()` methods. The type
|
|
9
|
+
* parameters track the validator shape, registered indexes, and search indexes
|
|
10
|
+
* so that {@link QueryBuilder} can enforce correct field names at compile time.
|
|
11
|
+
*
|
|
12
|
+
* You almost never reference `TableDefinition` directly — it is the value
|
|
13
|
+
* produced by `defineTable` and consumed internally by `defineSchema`.
|
|
14
|
+
*/
|
|
3
15
|
var TableDefinition = class {
|
|
16
|
+
validator;
|
|
4
17
|
indexes = [];
|
|
5
18
|
searchIndexes = [];
|
|
6
19
|
options;
|
|
@@ -8,6 +21,26 @@ var TableDefinition = class {
|
|
|
8
21
|
this.validator = validator;
|
|
9
22
|
this.options = options ?? {};
|
|
10
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Register a named index on one or more fields of this table.
|
|
26
|
+
*
|
|
27
|
+
* Indexes allow efficient range queries via `ctx.db.query(table).withIndex()`.
|
|
28
|
+
* The first field listed is the primary sort key; additional fields refine
|
|
29
|
+
* within equal values of the first key.
|
|
30
|
+
*
|
|
31
|
+
* **Rules**
|
|
32
|
+
* - An index must cover at least one field.
|
|
33
|
+
* - Field names must be top-level paths that exist in the table validator.
|
|
34
|
+
* - Index names must be unique within the table.
|
|
35
|
+
* - Index definitions are immutable after creation — changing the fields of
|
|
36
|
+
* an existing index requires a manual migration.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* defineTable({ status: s.string(), createdAt: s.number(), ownerId: s.string() })
|
|
40
|
+
* .index("by_owner_and_status", ["ownerId", "status"])
|
|
41
|
+
* .index("by_created", ["createdAt"])
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
11
44
|
index(name, fields) {
|
|
12
45
|
this.indexes.push({
|
|
13
46
|
name,
|
|
@@ -15,6 +48,34 @@ var TableDefinition = class {
|
|
|
15
48
|
});
|
|
16
49
|
return this;
|
|
17
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Register a named full-text search index on this table.
|
|
53
|
+
*
|
|
54
|
+
* Search indexes power `ctx.db.query(table).withSearchIndex()` queries. Each
|
|
55
|
+
* search index specifies:
|
|
56
|
+
* - A single `searchField` that is tokenised and indexed for full-text
|
|
57
|
+
* matching.
|
|
58
|
+
* - Zero or more `filterFields` that can be used to narrow results with
|
|
59
|
+
* equality conditions.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* defineTable({ title: s.string(), status: s.string(), ownerId: s.string() })
|
|
63
|
+
* .searchIndex("search_title", {
|
|
64
|
+
* searchField: "title",
|
|
65
|
+
* filterFields: ["status", "ownerId"],
|
|
66
|
+
* })
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* In a query handler:
|
|
70
|
+
* ```ts
|
|
71
|
+
* const results = await ctx.db
|
|
72
|
+
* .query("tasks")
|
|
73
|
+
* .withSearchIndex("search_title", (q) =>
|
|
74
|
+
* q.search("title", searchText).eq("status", "todo")
|
|
75
|
+
* )
|
|
76
|
+
* .collect();
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
18
79
|
searchIndex(name, config) {
|
|
19
80
|
this.searchIndexes.push({
|
|
20
81
|
name,
|
|
@@ -42,7 +103,16 @@ var TableDefinition = class {
|
|
|
42
103
|
function defineTable(validator) {
|
|
43
104
|
return new TableDefinition(isValidatorLike(validator) ? validator : ensureObjectValidator(validator));
|
|
44
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The typed data model produced by {@link defineSchema}.
|
|
108
|
+
*
|
|
109
|
+
* `SyncoreSchema` holds the table map and is the value you export from
|
|
110
|
+
* `syncore/schema.ts`. The runtime, context types, and codegen all reference
|
|
111
|
+
* this type to ensure end-to-end type safety between your schema definition
|
|
112
|
+
* and your function handlers.
|
|
113
|
+
*/
|
|
45
114
|
var SyncoreSchema = class {
|
|
115
|
+
tables;
|
|
46
116
|
constructor(tables) {
|
|
47
117
|
this.tables = tables;
|
|
48
118
|
}
|
|
@@ -55,6 +125,35 @@ var SyncoreSchema = class {
|
|
|
55
125
|
return Object.keys(this.tables);
|
|
56
126
|
}
|
|
57
127
|
};
|
|
128
|
+
/**
|
|
129
|
+
* Define the complete data model for a Syncore app.
|
|
130
|
+
*
|
|
131
|
+
* Pass an object whose keys are table names and values are `defineTable()`
|
|
132
|
+
* results. The resulting schema is passed to the runtime options and to the
|
|
133
|
+
* code generator.
|
|
134
|
+
*
|
|
135
|
+
* **Typical file: `syncore/schema.ts`**
|
|
136
|
+
*
|
|
137
|
+
* ```ts
|
|
138
|
+
* import { defineSchema, defineTable, s } from "syncorejs";
|
|
139
|
+
*
|
|
140
|
+
* export default defineSchema({
|
|
141
|
+
* tasks: defineTable({
|
|
142
|
+
* title: s.string(),
|
|
143
|
+
* status: s.enum(["todo", "done"] as const),
|
|
144
|
+
* projectId: s.nullable(s.id("projects")),
|
|
145
|
+
* })
|
|
146
|
+
* .index("by_project", ["projectId"]),
|
|
147
|
+
*
|
|
148
|
+
* projects: defineTable({
|
|
149
|
+
* name: s.string(),
|
|
150
|
+
* archivedAt: s.optional(s.number()),
|
|
151
|
+
* }),
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @param tables - A map of table names to their `TableDefinition` values.
|
|
156
|
+
*/
|
|
58
157
|
function defineSchema(tables) {
|
|
59
158
|
return new SyncoreSchema(tables);
|
|
60
159
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.js","names":[],"sources":["../src/definition.ts"],"sourcesContent":["import {\n describeValidator,\n deserializeValue,\n ensureObjectValidator,\n serializeValue,\n type FieldPaths,\n type Infer,\n type InferStorage,\n type ObjectValidator,\n type ObjectValidatorShape,\n type Validator\n} from \"./validators.js\";\n\ntype Expand<T> = { [TKey in keyof T]: T[TKey] } & {};\n\nexport interface IndexDefinition {\n name: string;\n fields: string[];\n}\n\nexport interface SearchIndexDefinition {\n name: string;\n searchField: string;\n filterFields: string[];\n}\n\nexport interface TableDefinitionOptions {\n tableName?: string;\n componentPath?: string;\n componentName?: string;\n}\n\nexport interface TableDocumentSystemFields {\n _id: string;\n _creationTime: number;\n}\n\nexport type GenericTableIndexes = Record<string, readonly string[]>;\n\nexport type GenericTableSearchIndexes = Record<\n string,\n {\n searchField: string;\n filterFields: string;\n }\n>;\n\nexport class TableDefinition<\n TValidator extends Validator<Record<string, unknown>, Record<string, unknown>, string>,\n TIndexes = Record<never, never>,\n TSearchIndexes = Record<never, never>\n> {\n readonly indexes: IndexDefinition[] = [];\n readonly searchIndexes: SearchIndexDefinition[] = [];\n readonly options: TableDefinitionOptions;\n\n declare readonly document: Infer<TValidator>;\n declare readonly storageDocument: InferStorage<TValidator>;\n declare readonly fieldPaths: FieldPaths<TValidator>;\n declare readonly indexesByName: TIndexes;\n declare readonly searchIndexesByName: TSearchIndexes;\n\n constructor(\n public readonly validator: TValidator,\n options?: TableDefinitionOptions\n ) {\n this.options = options ?? {};\n }\n\n index<\n const TIndexName extends string,\n TFirstField extends FieldPaths<TValidator>,\n TRestFields extends FieldPaths<TValidator>[]\n >(\n name: TIndexName,\n fields: [TFirstField, ...TRestFields]\n ): TableDefinition<\n TValidator,\n Expand<TIndexes & Record<TIndexName, readonly [TFirstField, ...TRestFields]>>,\n TSearchIndexes\n > {\n this.indexes.push({\n name,\n fields: [...fields]\n });\n return this as unknown as TableDefinition<\n TValidator,\n Expand<TIndexes & Record<TIndexName, readonly [TFirstField, ...TRestFields]>>,\n TSearchIndexes\n >;\n }\n\n searchIndex<\n const TIndexName extends string,\n TSearchField extends FieldPaths<TValidator>,\n TFilterField extends FieldPaths<TValidator> = never\n >(\n name: TIndexName,\n config: {\n searchField: TSearchField;\n filterFields?: TFilterField[];\n }\n ): TableDefinition<\n TValidator,\n TIndexes,\n Expand<\n TSearchIndexes &\n Record<\n TIndexName,\n {\n searchField: TSearchField;\n filterFields: TFilterField;\n }\n >\n >\n > {\n this.searchIndexes.push({\n name,\n searchField: config.searchField,\n filterFields: [...(config.filterFields ?? [])]\n });\n return this as unknown as TableDefinition<\n TValidator,\n TIndexes,\n Expand<\n TSearchIndexes &\n Record<\n TIndexName,\n {\n searchField: TSearchField;\n filterFields: TFilterField;\n }\n >\n >\n >;\n }\n\n parse(value: unknown): Infer<TValidator> {\n return this.validator.parse(value) as Infer<TValidator>;\n }\n\n serialize(value: Infer<TValidator>): InferStorage<TValidator> {\n return serializeValue(this.validator, value) as InferStorage<TValidator>;\n }\n\n deserialize(value: unknown): Infer<TValidator> {\n return deserializeValue(this.validator, value) as Infer<TValidator>;\n }\n\n parseAndSerialize(value: unknown): InferStorage<TValidator> {\n return this.serialize(this.parse(value));\n }\n\n describe() {\n return describeValidator(this.validator);\n }\n}\n\nexport type AnyTableDefinition = TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>,\n GenericTableIndexes,\n GenericTableSearchIndexes\n>;\n\nexport type InferDocument<TTable extends AnyTableDefinition> = Infer<\n TTable[\"validator\"]\n> &\n TableDocumentSystemFields;\n\nexport type InferTableInput<TTable extends AnyTableDefinition> = Infer<\n TTable[\"validator\"]\n>;\n\nexport type TableFieldPaths<TTable> = TTable extends TableDefinition<\n infer TValidator,\n unknown,\n unknown\n>\n ? FieldPaths<TValidator>\n : never;\n\nexport type TableIndexes<TTable> = TTable extends TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>,\n infer TIndexes,\n unknown\n>\n ? TIndexes\n : never;\n\nexport type TableSearchIndexes<TTable> = TTable extends TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>,\n unknown,\n infer TSearchIndexes\n>\n ? TSearchIndexes\n : never;\n\nexport type TableIndexNames<TTable> = Extract<\n keyof TableIndexes<TTable>,\n string\n>;\n\nexport type TableSearchIndexNames<TTable> = Extract<\n keyof TableSearchIndexes<TTable>,\n string\n>;\n\nexport type TableIndexFields<\n TTable,\n TIndexName extends TableIndexNames<TTable>\n> = TableIndexes<TTable>[TIndexName];\n\nexport type TableSearchIndexConfig<\n TTable,\n TIndexName extends TableSearchIndexNames<TTable>\n> = TableSearchIndexes<TTable>[TIndexName];\n\nexport type TableFieldDefinitionSummary = {\n name: string;\n validator: ReturnType<AnyTableDefinition[\"describe\"]>;\n storage: ReturnType<AnyTableDefinition[\"describe\"]>;\n optional: boolean;\n};\n\nexport function defineTable<const TShape extends ObjectValidatorShape>(\n validator: TShape\n): TableDefinition<ObjectValidator<TShape>>;\nexport function defineTable<\n TValidator extends Validator<Record<string, unknown>, Record<string, unknown>, string>\n>(validator: TValidator): TableDefinition<TValidator>;\nexport function defineTable<const TShape extends ObjectValidatorShape>(\n validator:\n | TShape\n | Validator<Record<string, unknown>, Record<string, unknown>, string>\n): TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>\n> {\n const normalized: Validator<Record<string, unknown>, Record<string, unknown>, string> =\n isValidatorLike(validator)\n ? validator\n : ensureObjectValidator(validator);\n return new TableDefinition(normalized);\n}\n\nexport interface SyncoreSchemaDefinition {\n [tableName: string]: AnyTableDefinition;\n}\n\nexport class SyncoreSchema<TTables> {\n constructor(public readonly tables: TTables) {}\n\n getTable<TTableName extends Extract<keyof TTables, string>>(\n tableName: TTableName\n ): TTables[TTableName] {\n const tables = this.tables as Record<string, unknown>;\n const table = tables[tableName];\n if (!table) {\n throw new Error(`Unknown table \"${tableName}\".`);\n }\n return table as TTables[TTableName];\n }\n\n tableNames(): Array<Extract<keyof TTables, string>> {\n return Object.keys(this.tables as Record<string, unknown>) as Array<\n Extract<keyof TTables, string>\n >;\n }\n}\n\nexport function defineSchema<const TTables extends SyncoreSchemaDefinition>(\n tables: TTables\n): SyncoreSchema<TTables> {\n return new SyncoreSchema(tables);\n}\n\nfunction isValidatorLike(\n value: Validator<Record<string, unknown>, Record<string, unknown>, string> | ObjectValidatorShape\n): value is Validator<Record<string, unknown>, Record<string, unknown>, string> {\n return typeof (value as Validator<unknown, unknown, string>).parse === \"function\";\n}\n"],"mappings":";;AA+CA,IAAa,kBAAb,MAIE;CACA,UAAsC,EAAE;CACxC,gBAAkD,EAAE;CACpD;CAQA,YACE,WACA,SACA;AAFgB,OAAA,YAAA;AAGhB,OAAK,UAAU,WAAW,EAAE;;CAG9B,MAKE,MACA,QAKA;AACA,OAAK,QAAQ,KAAK;GAChB;GACA,QAAQ,CAAC,GAAG,OAAO;GACpB,CAAC;AACF,SAAO;;CAOT,YAKE,MACA,QAiBA;AACA,OAAK,cAAc,KAAK;GACtB;GACA,aAAa,OAAO;GACpB,cAAc,CAAC,GAAI,OAAO,gBAAgB,EAAE,CAAE;GAC/C,CAAC;AACF,SAAO;;CAgBT,MAAM,OAAmC;AACvC,SAAO,KAAK,UAAU,MAAM,MAAM;;CAGpC,UAAU,OAAoD;AAC5D,SAAO,eAAe,KAAK,WAAW,MAAM;;CAG9C,YAAY,OAAmC;AAC7C,SAAO,iBAAiB,KAAK,WAAW,MAAM;;CAGhD,kBAAkB,OAA0C;AAC1D,SAAO,KAAK,UAAU,KAAK,MAAM,MAAM,CAAC;;CAG1C,WAAW;AACT,SAAO,kBAAkB,KAAK,UAAU;;;AA4E5C,SAAgB,YACd,WAKA;AAKA,QAAO,IAAI,gBAHT,gBAAgB,UAAU,GACxB,YACA,sBAAsB,UAAU,CACE;;AAOxC,IAAa,gBAAb,MAAoC;CAClC,YAAY,QAAiC;AAAjB,OAAA,SAAA;;CAE5B,SACE,WACqB;EAErB,MAAM,QADS,KAAK,OACC;AACrB,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,kBAAkB,UAAU,IAAI;AAElD,SAAO;;CAGT,aAAoD;AAClD,SAAO,OAAO,KAAK,KAAK,OAAkC;;;AAM9D,SAAgB,aACd,QACwB;AACxB,QAAO,IAAI,cAAc,OAAO;;AAGlC,SAAS,gBACP,OAC8E;AAC9E,QAAO,OAAQ,MAA8C,UAAU"}
|
|
1
|
+
{"version":3,"file":"definition.js","names":[],"sources":["../src/definition.ts"],"sourcesContent":["import {\n describeValidator,\n deserializeValue,\n ensureObjectValidator,\n serializeValue,\n type FieldPaths,\n type Infer,\n type InferStorage,\n type ObjectValidator,\n type ObjectValidatorShape,\n type Validator\n} from \"./validators.js\";\n\ntype Expand<T> = { [TKey in keyof T]: T[TKey] } & {};\n\nexport interface IndexDefinition {\n name: string;\n fields: string[];\n}\n\nexport interface SearchIndexDefinition {\n name: string;\n searchField: string;\n filterFields: string[];\n}\n\nexport interface TableDefinitionOptions {\n tableName?: string;\n componentPath?: string;\n componentName?: string;\n}\n\n/**\n * The built-in system fields automatically added to every Syncore document.\n *\n * You never supply these when inserting; Syncore sets them for you.\n * - `_id` — A unique string identifier for the document. Use `s.id(tableName)` to\n * type foreign-key fields that reference this value.\n * - `_creationTime` — Unix timestamp (milliseconds) when the document was inserted.\n */\nexport interface TableDocumentSystemFields {\n _id: string;\n _creationTime: number;\n}\n\nexport type GenericTableIndexes = Record<string, readonly string[]>;\n\nexport type GenericTableSearchIndexes = Record<\n string,\n {\n searchField: string;\n filterFields: string;\n }\n>;\n\n/**\n * A typed table definition that describes the shape, indexes, and search\n * indexes for a single Syncore table.\n *\n * You create `TableDefinition` instances with {@link defineTable} and attach\n * indexes with the fluent `.index()` and `.searchIndex()` methods. The type\n * parameters track the validator shape, registered indexes, and search indexes\n * so that {@link QueryBuilder} can enforce correct field names at compile time.\n *\n * You almost never reference `TableDefinition` directly — it is the value\n * produced by `defineTable` and consumed internally by `defineSchema`.\n */\nexport class TableDefinition<\n TValidator extends Validator<Record<string, unknown>, Record<string, unknown>, string>,\n TIndexes = Record<never, never>,\n TSearchIndexes = Record<never, never>\n> {\n readonly indexes: IndexDefinition[] = [];\n readonly searchIndexes: SearchIndexDefinition[] = [];\n readonly options: TableDefinitionOptions;\n\n declare readonly document: Infer<TValidator>;\n declare readonly storageDocument: InferStorage<TValidator>;\n declare readonly fieldPaths: FieldPaths<TValidator>;\n declare readonly indexesByName: TIndexes;\n declare readonly searchIndexesByName: TSearchIndexes;\n\n constructor(\n public readonly validator: TValidator,\n options?: TableDefinitionOptions\n ) {\n this.options = options ?? {};\n }\n\n /**\n * Register a named index on one or more fields of this table.\n *\n * Indexes allow efficient range queries via `ctx.db.query(table).withIndex()`.\n * The first field listed is the primary sort key; additional fields refine\n * within equal values of the first key.\n *\n * **Rules**\n * - An index must cover at least one field.\n * - Field names must be top-level paths that exist in the table validator.\n * - Index names must be unique within the table.\n * - Index definitions are immutable after creation — changing the fields of\n * an existing index requires a manual migration.\n *\n * ```ts\n * defineTable({ status: s.string(), createdAt: s.number(), ownerId: s.string() })\n * .index(\"by_owner_and_status\", [\"ownerId\", \"status\"])\n * .index(\"by_created\", [\"createdAt\"])\n * ```\n */\n index<\n const TIndexName extends string,\n TFirstField extends FieldPaths<TValidator>,\n TRestFields extends FieldPaths<TValidator>[]\n >(\n name: TIndexName,\n fields: [TFirstField, ...TRestFields]\n ): TableDefinition<\n TValidator,\n Expand<TIndexes & Record<TIndexName, readonly [TFirstField, ...TRestFields]>>,\n TSearchIndexes\n > {\n this.indexes.push({\n name,\n fields: [...fields]\n });\n return this as unknown as TableDefinition<\n TValidator,\n Expand<TIndexes & Record<TIndexName, readonly [TFirstField, ...TRestFields]>>,\n TSearchIndexes\n >;\n }\n\n /**\n * Register a named full-text search index on this table.\n *\n * Search indexes power `ctx.db.query(table).withSearchIndex()` queries. Each\n * search index specifies:\n * - A single `searchField` that is tokenised and indexed for full-text\n * matching.\n * - Zero or more `filterFields` that can be used to narrow results with\n * equality conditions.\n *\n * ```ts\n * defineTable({ title: s.string(), status: s.string(), ownerId: s.string() })\n * .searchIndex(\"search_title\", {\n * searchField: \"title\",\n * filterFields: [\"status\", \"ownerId\"],\n * })\n * ```\n *\n * In a query handler:\n * ```ts\n * const results = await ctx.db\n * .query(\"tasks\")\n * .withSearchIndex(\"search_title\", (q) =>\n * q.search(\"title\", searchText).eq(\"status\", \"todo\")\n * )\n * .collect();\n * ```\n */\n searchIndex<\n const TIndexName extends string,\n TSearchField extends FieldPaths<TValidator>,\n TFilterField extends FieldPaths<TValidator> = never\n >(\n name: TIndexName,\n config: {\n searchField: TSearchField;\n filterFields?: TFilterField[];\n }\n ): TableDefinition<\n TValidator,\n TIndexes,\n Expand<\n TSearchIndexes &\n Record<\n TIndexName,\n {\n searchField: TSearchField;\n filterFields: TFilterField;\n }\n >\n >\n > {\n this.searchIndexes.push({\n name,\n searchField: config.searchField,\n filterFields: [...(config.filterFields ?? [])]\n });\n return this as unknown as TableDefinition<\n TValidator,\n TIndexes,\n Expand<\n TSearchIndexes &\n Record<\n TIndexName,\n {\n searchField: TSearchField;\n filterFields: TFilterField;\n }\n >\n >\n >;\n }\n\n parse(value: unknown): Infer<TValidator> {\n return this.validator.parse(value) as Infer<TValidator>;\n }\n\n serialize(value: Infer<TValidator>): InferStorage<TValidator> {\n return serializeValue(this.validator, value) as InferStorage<TValidator>;\n }\n\n deserialize(value: unknown): Infer<TValidator> {\n return deserializeValue(this.validator, value) as Infer<TValidator>;\n }\n\n parseAndSerialize(value: unknown): InferStorage<TValidator> {\n return this.serialize(this.parse(value));\n }\n\n describe() {\n return describeValidator(this.validator);\n }\n}\n\nexport type AnyTableDefinition = TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>,\n GenericTableIndexes,\n GenericTableSearchIndexes\n>;\n\nexport type InferDocument<TTable extends AnyTableDefinition> = Infer<\n TTable[\"validator\"]\n> &\n TableDocumentSystemFields;\n\nexport type InferTableInput<TTable extends AnyTableDefinition> = Infer<\n TTable[\"validator\"]\n>;\n\nexport type TableFieldPaths<TTable> = TTable extends TableDefinition<\n infer TValidator,\n unknown,\n unknown\n>\n ? FieldPaths<TValidator>\n : never;\n\nexport type TableIndexes<TTable> = TTable extends TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>,\n infer TIndexes,\n unknown\n>\n ? TIndexes\n : never;\n\nexport type TableSearchIndexes<TTable> = TTable extends TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>,\n unknown,\n infer TSearchIndexes\n>\n ? TSearchIndexes\n : never;\n\nexport type TableIndexNames<TTable> = Extract<\n keyof TableIndexes<TTable>,\n string\n>;\n\nexport type TableSearchIndexNames<TTable> = Extract<\n keyof TableSearchIndexes<TTable>,\n string\n>;\n\nexport type TableIndexFields<\n TTable,\n TIndexName extends TableIndexNames<TTable>\n> = TableIndexes<TTable>[TIndexName];\n\nexport type TableSearchIndexConfig<\n TTable,\n TIndexName extends TableSearchIndexNames<TTable>\n> = TableSearchIndexes<TTable>[TIndexName];\n\nexport type TableFieldDefinitionSummary = {\n name: string;\n validator: ReturnType<AnyTableDefinition[\"describe\"]>;\n storage: ReturnType<AnyTableDefinition[\"describe\"]>;\n optional: boolean;\n};\n\n/**\n * Define a Syncore table by specifying its field validators.\n *\n * `defineTable` is the building block of your data model. Pass a validator map\n * (keys are field names, values are `s.*` validators) or a single `s.object()`\n * validator. Chain `.index()` and `.searchIndex()` to register query indexes.\n *\n * The system fields `_id` and `_creationTime` are added automatically and\n * should not be included in the shape.\n *\n * ```ts\n * import { defineTable, s } from \"syncorejs\";\n *\n * const tasks = defineTable({\n * title: s.string(),\n * status: s.enum([\"todo\", \"done\"] as const),\n * projectId: s.nullable(s.id(\"projects\")),\n * dueAt: s.optional(s.number()),\n * })\n * .index(\"by_project\", [\"projectId\"])\n * .index(\"by_status\", [\"status\"])\n * .searchIndex(\"search_title\", { searchField: \"title\", filterFields: [\"status\"] });\n * ```\n */\nexport function defineTable<const TShape extends ObjectValidatorShape>(\n validator: TShape\n): TableDefinition<ObjectValidator<TShape>>;\nexport function defineTable<\n TValidator extends Validator<Record<string, unknown>, Record<string, unknown>, string>\n>(validator: TValidator): TableDefinition<TValidator>;\nexport function defineTable<const TShape extends ObjectValidatorShape>(\n validator:\n | TShape\n | Validator<Record<string, unknown>, Record<string, unknown>, string>\n): TableDefinition<\n Validator<Record<string, unknown>, Record<string, unknown>, string>\n> {\n const normalized: Validator<Record<string, unknown>, Record<string, unknown>, string> =\n isValidatorLike(validator)\n ? validator\n : ensureObjectValidator(validator);\n return new TableDefinition(normalized);\n}\n\nexport interface SyncoreSchemaDefinition {\n [tableName: string]: AnyTableDefinition;\n}\n\n/**\n * The typed data model produced by {@link defineSchema}.\n *\n * `SyncoreSchema` holds the table map and is the value you export from\n * `syncore/schema.ts`. The runtime, context types, and codegen all reference\n * this type to ensure end-to-end type safety between your schema definition\n * and your function handlers.\n */\nexport class SyncoreSchema<TTables> {\n constructor(public readonly tables: TTables) {}\n\n getTable<TTableName extends Extract<keyof TTables, string>>(\n tableName: TTableName\n ): TTables[TTableName] {\n const tables = this.tables as Record<string, unknown>;\n const table = tables[tableName];\n if (!table) {\n throw new Error(`Unknown table \"${tableName}\".`);\n }\n return table as TTables[TTableName];\n }\n\n tableNames(): Array<Extract<keyof TTables, string>> {\n return Object.keys(this.tables as Record<string, unknown>) as Array<\n Extract<keyof TTables, string>\n >;\n }\n}\n\n/**\n * Define the complete data model for a Syncore app.\n *\n * Pass an object whose keys are table names and values are `defineTable()`\n * results. The resulting schema is passed to the runtime options and to the\n * code generator.\n *\n * **Typical file: `syncore/schema.ts`**\n *\n * ```ts\n * import { defineSchema, defineTable, s } from \"syncorejs\";\n *\n * export default defineSchema({\n * tasks: defineTable({\n * title: s.string(),\n * status: s.enum([\"todo\", \"done\"] as const),\n * projectId: s.nullable(s.id(\"projects\")),\n * })\n * .index(\"by_project\", [\"projectId\"]),\n *\n * projects: defineTable({\n * name: s.string(),\n * archivedAt: s.optional(s.number()),\n * }),\n * });\n * ```\n *\n * @param tables - A map of table names to their `TableDefinition` values.\n */\nexport function defineSchema<const TTables extends SyncoreSchemaDefinition>(\n tables: TTables\n): SyncoreSchema<TTables> {\n return new SyncoreSchema(tables);\n}\n\nfunction isValidatorLike(\n value: Validator<Record<string, unknown>, Record<string, unknown>, string> | ObjectValidatorShape\n): value is Validator<Record<string, unknown>, Record<string, unknown>, string> {\n return typeof (value as Validator<unknown, unknown, string>).parse === \"function\";\n}\n"],"mappings":";;;;;;;;;;;;;;AAmEA,IAAa,kBAAb,MAIE;CAYkB;CAXlB,UAAsC,CAAC;CACvC,gBAAkD,CAAC;CACnD;CAQA,YACE,WACA,SACA;EAFgB,KAAA,YAAA;EAGhB,KAAK,UAAU,WAAW,CAAC;CAC7B;;;;;;;;;;;;;;;;;;;;;CAsBA,MAKE,MACA,QAKA;EACA,KAAK,QAAQ,KAAK;GAChB;GACA,QAAQ,CAAC,GAAG,MAAM;EACpB,CAAC;EACD,OAAO;CAKT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BA,YAKE,MACA,QAiBA;EACA,KAAK,cAAc,KAAK;GACtB;GACA,aAAa,OAAO;GACpB,cAAc,CAAC,GAAI,OAAO,gBAAgB,CAAC,CAAE;EAC/C,CAAC;EACD,OAAO;CAcT;CAEA,MAAM,OAAmC;EACvC,OAAO,KAAK,UAAU,MAAM,KAAK;CACnC;CAEA,UAAU,OAAoD;EAC5D,OAAO,eAAe,KAAK,WAAW,KAAK;CAC7C;CAEA,YAAY,OAAmC;EAC7C,OAAO,iBAAiB,KAAK,WAAW,KAAK;CAC/C;CAEA,kBAAkB,OAA0C;EAC1D,OAAO,KAAK,UAAU,KAAK,MAAM,KAAK,CAAC;CACzC;CAEA,WAAW;EACT,OAAO,kBAAkB,KAAK,SAAS;CACzC;AACF;AAkGA,SAAgB,YACd,WAKA;CAKA,OAAO,IAAI,gBAHT,gBAAgB,SAAS,IACvB,YACA,sBAAsB,SAAS,CACE;AACvC;;;;;;;;;AAcA,IAAa,gBAAb,MAAoC;CACN;CAA5B,YAAY,QAAiC;EAAjB,KAAA,SAAA;CAAkB;CAE9C,SACE,WACqB;EAErB,MAAM,QADS,KAAK,OACC;EACrB,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,kBAAkB,UAAU,GAAG;EAEjD,OAAO;CACT;CAEA,aAAoD;EAClD,OAAO,OAAO,KAAK,KAAK,MAAiC;CAG3D;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,aACd,QACwB;CACxB,OAAO,IAAI,cAAc,MAAM;AACjC;AAEA,SAAS,gBACP,OAC8E;CAC9E,OAAO,OAAQ,MAA8C,UAAU;AACzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner.d.ts","names":[],"sources":["../src/planner.ts"],"mappings":";;;;UAUiB,kBAAA;EACf,IAAA;EACA,SAAA,EAAW,oBAAA;EACX,OAAA,EAAS,
|
|
1
|
+
{"version":3,"file":"planner.d.ts","names":[],"sources":["../src/planner.ts"],"mappings":";;;;UAUiB,kBAAA;EACf,IAAA;EACA,SAAA,EAAW,oBAAA;EACX,OAAA,EAAS,oBAAoB;EAC7B,QAAA;AAAA;AAAA,UAGe,aAAA;EACf,IAAA;EACA,WAAA;EACA,aAAA;EACA,aAAA;EACA,SAAA,EAAW,oBAAA;EACX,UAAA;EACA,MAAA,EAAQ,kBAAA;EACR,OAAA,EAAS,KAAA;IACP,IAAA;IACA,MAAA;EAAA;EAEF,aAAA,EAAe,KAAA;IACb,IAAA;IACA,WAAA;IACA,YAAA;EAAA;AAAA;AAAA,UAIa,cAAA;EACf,aAAA;EACA,cAAA;EACA,cAAA;EACA,MAAA,EAAQ,aAAa;EACrB,IAAA;AAAA;AAAA,UAGe,mBAAA;EACf,aAAA;EACA,cAAA;EACA,YAAA;EACA,QAAA;EACA,cAAA;EACA,YAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;AAAA;AAAA,iBAGc,oBAAA,iBAAqC,uBAAA,CAAA,CACnD,MAAA,EAAQ,aAAA,CAAc,OAAA,IACrB,cAAA;AAAA,iBA+Ca,mBAAA,CACd,gBAAA,EAAkB,cAAA,qBAClB,YAAA,EAAc,cAAA,GACb,mBAAA;AAAA,iBAyGa,kBAAA,CACd,IAAA,EAAM,mBAAmB,EACzB,OAAA;EAAY,KAAA;AAAA;AAAA,iBAiCE,mBAAA,CAAoB,MAAA,WAAiB,cAAc;AAAA,iBAwEnD,0BAAA,CAA2B,SAAiB;AAAA,iBAS5C,0BAAA,CACd,SAAA,UACA,SAAA,UACA,MAAA;AAAA,iBAUc,gCAAA,CACd,SAAA,UACA,WAAA,EAAa,qBAAA,GAAwB,aAAa;AAAA,iBAOpC,oBAAA,CAAqB,SAAA,UAAmB,SAAiB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner.js","names":[],"sources":["../src/planner.ts"],"sourcesContent":["import {\n type SearchIndexDefinition,\n type SyncoreSchemaDefinition,\n type SyncoreSchema\n} from \"./definition.js\";\nimport {\n describeValidator,\n type ValidatorDescription\n} from \"./validators.js\";\n\nexport interface TableFieldSnapshot {\n name: string;\n validator: ValidatorDescription;\n storage: ValidatorDescription;\n optional: boolean;\n}\n\nexport interface TableSnapshot {\n name: string;\n displayName?: string;\n componentPath?: string;\n componentName?: string;\n validator: ValidatorDescription;\n fieldPaths: string[];\n fields: TableFieldSnapshot[];\n indexes: Array<{\n name: string;\n fields: string[];\n }>;\n searchIndexes: Array<{\n name: string;\n searchField: string;\n filterFields: string[];\n }>;\n}\n\nexport interface SchemaSnapshot {\n formatVersion: 3;\n plannerVersion: 2;\n runtimeVersion?: string;\n tables: TableSnapshot[];\n hash: string;\n}\n\nexport interface SchemaMigrationPlan {\n formatVersion: 3;\n plannerVersion: 2;\n previousHash: string | null;\n nextHash: string;\n fromSchemaHash: string | null;\n toSchemaHash: string;\n statements: string[];\n warnings: string[];\n destructiveChanges: string[];\n}\n\nexport function createSchemaSnapshot<TTables extends SyncoreSchemaDefinition>(\n schema: SyncoreSchema<TTables>\n): SchemaSnapshot {\n const tables = schema\n .tableNames()\n .sort((left, right) => left.localeCompare(right))\n .map((tableName) => {\n const table = schema.getTable(tableName);\n const validator = describeValidator(table.validator);\n return {\n name: tableName,\n ...(table.options.tableName ? { displayName: table.options.tableName } : {}),\n ...(table.options.componentPath\n ? { componentPath: table.options.componentPath }\n : {}),\n ...(table.options.componentName\n ? { componentName: table.options.componentName }\n : {}),\n validator,\n fieldPaths: extractFieldPaths(validator),\n fields: extractTopLevelFields(validator),\n indexes: table.indexes\n .map((index) => ({\n name: index.name,\n fields: [...index.fields]\n }))\n .sort((left, right) => left.name.localeCompare(right.name)),\n searchIndexes: table.searchIndexes\n .map((index) => ({\n name: index.name,\n searchField: index.searchField,\n filterFields: [...index.filterFields]\n }))\n .sort((left, right) => left.name.localeCompare(right.name))\n };\n });\n\n const base = {\n formatVersion: 3 as const,\n plannerVersion: 2 as const,\n tables\n };\n\n return {\n ...base,\n hash: createSchemaHash(base)\n };\n}\n\nexport function diffSchemaSnapshots(\n previousSnapshot: SchemaSnapshot | null | undefined,\n nextSnapshot: SchemaSnapshot\n): SchemaMigrationPlan {\n const statements: string[] = [];\n const warnings: string[] = [];\n const destructiveChanges: string[] = [];\n\n const previousTables = new Map(\n (previousSnapshot?.tables ?? []).map((table) => [table.name, table])\n );\n const nextTables = new Map(nextSnapshot.tables.map((table) => [table.name, table]));\n\n for (const table of nextSnapshot.tables) {\n const previousTable = previousTables.get(table.name);\n if (!previousTable) {\n statements.push(renderCreateTableStatement(table.name));\n for (const index of table.indexes) {\n statements.push(renderCreateIndexStatement(table.name, index.name, index.fields));\n }\n for (const searchIndex of table.searchIndexes) {\n statements.push(renderCreateSearchIndexStatement(table.name, searchIndex));\n }\n continue;\n }\n\n if (stableStringify(previousTable.validator) !== stableStringify(table.validator)) {\n warnings.push(\n `Validator changed for table \"${table.name}\". Existing rows are not rewritten automatically.`\n );\n }\n\n const previousIndexes = new Map(\n previousTable.indexes.map((index) => [index.name, index])\n );\n const nextIndexes = new Map(table.indexes.map((index) => [index.name, index]));\n\n for (const index of table.indexes) {\n const previousIndex = previousIndexes.get(index.name);\n if (!previousIndex) {\n statements.push(renderCreateIndexStatement(table.name, index.name, index.fields));\n continue;\n }\n if (stableStringify(previousIndex.fields) !== stableStringify(index.fields)) {\n destructiveChanges.push(\n `Index \"${table.name}.${index.name}\" changed fields and requires a manual migration.`\n );\n }\n }\n\n for (const previousIndex of previousTable.indexes) {\n if (!nextIndexes.has(previousIndex.name)) {\n destructiveChanges.push(\n `Index \"${table.name}.${previousIndex.name}\" was removed and requires a manual migration.`\n );\n }\n }\n\n const previousSearchIndexes = new Map(\n previousTable.searchIndexes.map((index) => [index.name, index])\n );\n const nextSearchIndexes = new Map(\n table.searchIndexes.map((index) => [index.name, index])\n );\n\n for (const searchIndex of table.searchIndexes) {\n const previousSearchIndex = previousSearchIndexes.get(searchIndex.name);\n if (!previousSearchIndex) {\n statements.push(renderCreateSearchIndexStatement(table.name, searchIndex));\n continue;\n }\n if (stableStringify(previousSearchIndex) !== stableStringify(searchIndex)) {\n destructiveChanges.push(\n `Search index \"${table.name}.${searchIndex.name}\" changed and requires a manual migration.`\n );\n }\n }\n\n for (const previousSearchIndex of previousTable.searchIndexes) {\n if (!nextSearchIndexes.has(previousSearchIndex.name)) {\n destructiveChanges.push(\n `Search index \"${table.name}.${previousSearchIndex.name}\" was removed and requires a manual migration.`\n );\n }\n }\n }\n\n for (const previousTable of previousSnapshot?.tables ?? []) {\n if (!nextTables.has(previousTable.name)) {\n destructiveChanges.push(\n `Table \"${previousTable.name}\" was removed and requires a manual migration.`\n );\n }\n }\n\n return {\n formatVersion: 3,\n plannerVersion: 2,\n previousHash: previousSnapshot?.hash ?? null,\n nextHash: nextSnapshot.hash,\n fromSchemaHash: previousSnapshot?.hash ?? null,\n toSchemaHash: nextSnapshot.hash,\n statements,\n warnings,\n destructiveChanges\n };\n}\n\nexport function renderMigrationSql(\n plan: SchemaMigrationPlan,\n options?: { title?: string }\n): string {\n const lines: string[] = [];\n\n lines.push(`-- ${options?.title ?? \"Syncore migration\"}`);\n lines.push(`-- format-version: ${plan.formatVersion}`);\n lines.push(`-- planner-version: ${plan.plannerVersion}`);\n lines.push(`-- previous: ${plan.previousHash ?? \"none\"}`);\n lines.push(`-- next: ${plan.nextHash}`);\n\n for (const warning of plan.warnings) {\n lines.push(`-- warning: ${warning}`);\n }\n\n if (plan.destructiveChanges.length > 0) {\n for (const destructiveChange of plan.destructiveChanges) {\n lines.push(`-- destructive: ${destructiveChange}`);\n }\n }\n\n if (plan.statements.length > 0) {\n lines.push(\"\");\n for (const statement of plan.statements) {\n lines.push(statement);\n }\n } else {\n lines.push(\"\");\n lines.push(\"-- no-op\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function parseSchemaSnapshot(source: string): SchemaSnapshot {\n const parsed = JSON.parse(source) as\n | SchemaSnapshot\n | {\n formatVersion: 2;\n plannerVersion: 1;\n runtimeVersion?: string;\n tables: Array<{\n name: string;\n displayName?: string;\n componentPath?: string;\n componentName?: string;\n validator: ValidatorDescription;\n indexes: Array<{ name: string; fields: string[] }>;\n searchIndexes: Array<{\n name: string;\n searchField: string;\n filterFields: string[];\n }>;\n }>;\n hash: string;\n }\n | {\n version: 1;\n tables: TableSnapshot[];\n hash: string;\n };\n\n if (\"formatVersion\" in parsed && parsed.formatVersion === 3) {\n if (\n parsed.plannerVersion !== 2 ||\n !Array.isArray(parsed.tables) ||\n typeof parsed.hash !== \"string\"\n ) {\n throw new Error(\"Invalid schema snapshot file.\");\n }\n return parsed;\n }\n\n if (\"formatVersion\" in parsed && parsed.formatVersion === 2) {\n return {\n formatVersion: 3,\n plannerVersion: 2,\n ...(parsed.runtimeVersion ? { runtimeVersion: parsed.runtimeVersion } : {}),\n tables: parsed.tables.map((table) => ({\n ...table,\n fieldPaths: extractFieldPaths(table.validator),\n fields: extractTopLevelFields(table.validator)\n })),\n hash: parsed.hash\n };\n }\n\n if (\n parsed.version !== 1 ||\n !Array.isArray(parsed.tables) ||\n typeof parsed.hash !== \"string\"\n ) {\n throw new Error(\"Invalid schema snapshot file.\");\n }\n return {\n formatVersion: 3,\n plannerVersion: 2,\n tables: parsed.tables.map((table) => ({\n ...table,\n fieldPaths: table.fieldPaths ?? extractFieldPaths(table.validator),\n fields: table.fields ?? extractTopLevelFields(table.validator)\n })),\n hash: parsed.hash\n };\n}\n\nexport function renderCreateTableStatement(tableName: string): string {\n return `\nCREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)} (\n _id TEXT PRIMARY KEY,\n _creationTime INTEGER NOT NULL,\n _json TEXT NOT NULL\n);`.trim();\n}\n\nexport function renderCreateIndexStatement(\n tableName: string,\n indexName: string,\n fields: string[]\n): string {\n const expressions = fields\n .map((field) => `json_extract(_json, '$.${field}')`)\n .join(\", \");\n return `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(\n `idx_${tableName}_${indexName}`\n )} ON ${quoteIdentifier(tableName)} (${expressions});`;\n}\n\nexport function renderCreateSearchIndexStatement(\n tableName: string,\n searchIndex: SearchIndexDefinition | TableSnapshot[\"searchIndexes\"][number]\n): string {\n return `CREATE VIRTUAL TABLE IF NOT EXISTS ${quoteIdentifier(\n searchIndexTableName(tableName, searchIndex.name)\n )} USING fts5(_id UNINDEXED, search_value);`;\n}\n\nexport function searchIndexTableName(tableName: string, indexName: string): string {\n return `fts_${tableName}_${indexName}`;\n}\n\nfunction createSchemaHash(\n value: Omit<SchemaSnapshot, \"hash\" | \"runtimeVersion\"> & {\n runtimeVersion?: string;\n }\n): string {\n return stableStringify(value);\n}\n\nfunction quoteIdentifier(identifier: string): string {\n return `\"${identifier.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortValue);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, sortValue(nested)])\n );\n }\n return value;\n}\n\nfunction extractFieldPaths(\n description: ValidatorDescription,\n prefix = \"\"\n): string[] {\n switch (description.kind) {\n case \"object\":\n return Object.entries(description.shape).flatMap(([key, entry]) => {\n const path = prefix ? `${prefix}.${key}` : key;\n const normalizedEntry = normalizeObjectFieldEntry(entry);\n const nested = extractFieldPaths(normalizedEntry.validator, path);\n return nested.length > 0 ? [path, ...nested] : [path];\n });\n case \"optional\":\n return extractFieldPaths(description.inner, prefix);\n case \"codec\":\n return extractFieldPaths(description.value, prefix);\n case \"union\":\n return [...new Set(description.members.flatMap((member) => extractFieldPaths(member, prefix)))];\n default:\n return [];\n }\n}\n\nfunction extractTopLevelFields(\n description: ValidatorDescription\n): TableFieldSnapshot[] {\n if (description.kind !== \"object\") {\n return [];\n }\n return Object.entries(description.shape)\n .map(([name, entry]) => {\n const normalizedEntry = normalizeObjectFieldEntry(entry);\n return {\n name,\n validator: normalizedEntry.validator,\n storage:\n normalizedEntry.validator.kind === \"codec\"\n ? normalizedEntry.validator.storage\n : normalizedEntry.validator,\n optional: normalizedEntry.optional\n };\n })\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction normalizeObjectFieldEntry(\n entry:\n | ValidatorDescription\n | {\n validator: ValidatorDescription;\n optional?: boolean;\n }\n): {\n validator: ValidatorDescription;\n optional: boolean;\n} {\n if (\"validator\" in entry) {\n return {\n validator: entry.validator,\n optional: entry.optional ?? false\n };\n }\n if (entry.kind === \"optional\") {\n return {\n validator: entry.inner,\n optional: true\n };\n }\n return {\n validator: entry,\n optional: false\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner.js","names":[],"sources":["../src/planner.ts"],"sourcesContent":["import {\n type SearchIndexDefinition,\n type SyncoreSchemaDefinition,\n type SyncoreSchema\n} from \"./definition.js\";\nimport {\n describeValidator,\n type ValidatorDescription\n} from \"./validators.js\";\n\nexport interface TableFieldSnapshot {\n name: string;\n validator: ValidatorDescription;\n storage: ValidatorDescription;\n optional: boolean;\n}\n\nexport interface TableSnapshot {\n name: string;\n displayName?: string;\n componentPath?: string;\n componentName?: string;\n validator: ValidatorDescription;\n fieldPaths: string[];\n fields: TableFieldSnapshot[];\n indexes: Array<{\n name: string;\n fields: string[];\n }>;\n searchIndexes: Array<{\n name: string;\n searchField: string;\n filterFields: string[];\n }>;\n}\n\nexport interface SchemaSnapshot {\n formatVersion: 3;\n plannerVersion: 2;\n runtimeVersion?: string;\n tables: TableSnapshot[];\n hash: string;\n}\n\nexport interface SchemaMigrationPlan {\n formatVersion: 3;\n plannerVersion: 2;\n previousHash: string | null;\n nextHash: string;\n fromSchemaHash: string | null;\n toSchemaHash: string;\n statements: string[];\n warnings: string[];\n destructiveChanges: string[];\n}\n\nexport function createSchemaSnapshot<TTables extends SyncoreSchemaDefinition>(\n schema: SyncoreSchema<TTables>\n): SchemaSnapshot {\n const tables = schema\n .tableNames()\n .sort((left, right) => left.localeCompare(right))\n .map((tableName) => {\n const table = schema.getTable(tableName);\n const validator = describeValidator(table.validator);\n return {\n name: tableName,\n ...(table.options.tableName ? { displayName: table.options.tableName } : {}),\n ...(table.options.componentPath\n ? { componentPath: table.options.componentPath }\n : {}),\n ...(table.options.componentName\n ? { componentName: table.options.componentName }\n : {}),\n validator,\n fieldPaths: extractFieldPaths(validator),\n fields: extractTopLevelFields(validator),\n indexes: table.indexes\n .map((index) => ({\n name: index.name,\n fields: [...index.fields]\n }))\n .sort((left, right) => left.name.localeCompare(right.name)),\n searchIndexes: table.searchIndexes\n .map((index) => ({\n name: index.name,\n searchField: index.searchField,\n filterFields: [...index.filterFields]\n }))\n .sort((left, right) => left.name.localeCompare(right.name))\n };\n });\n\n const base = {\n formatVersion: 3 as const,\n plannerVersion: 2 as const,\n tables\n };\n\n return {\n ...base,\n hash: createSchemaHash(base)\n };\n}\n\nexport function diffSchemaSnapshots(\n previousSnapshot: SchemaSnapshot | null | undefined,\n nextSnapshot: SchemaSnapshot\n): SchemaMigrationPlan {\n const statements: string[] = [];\n const warnings: string[] = [];\n const destructiveChanges: string[] = [];\n\n const previousTables = new Map(\n (previousSnapshot?.tables ?? []).map((table) => [table.name, table])\n );\n const nextTables = new Map(nextSnapshot.tables.map((table) => [table.name, table]));\n\n for (const table of nextSnapshot.tables) {\n const previousTable = previousTables.get(table.name);\n if (!previousTable) {\n statements.push(renderCreateTableStatement(table.name));\n for (const index of table.indexes) {\n statements.push(renderCreateIndexStatement(table.name, index.name, index.fields));\n }\n for (const searchIndex of table.searchIndexes) {\n statements.push(renderCreateSearchIndexStatement(table.name, searchIndex));\n }\n continue;\n }\n\n if (stableStringify(previousTable.validator) !== stableStringify(table.validator)) {\n warnings.push(\n `Validator changed for table \"${table.name}\". Existing rows are not rewritten automatically.`\n );\n }\n\n const previousIndexes = new Map(\n previousTable.indexes.map((index) => [index.name, index])\n );\n const nextIndexes = new Map(table.indexes.map((index) => [index.name, index]));\n\n for (const index of table.indexes) {\n const previousIndex = previousIndexes.get(index.name);\n if (!previousIndex) {\n statements.push(renderCreateIndexStatement(table.name, index.name, index.fields));\n continue;\n }\n if (stableStringify(previousIndex.fields) !== stableStringify(index.fields)) {\n destructiveChanges.push(\n `Index \"${table.name}.${index.name}\" changed fields and requires a manual migration.`\n );\n }\n }\n\n for (const previousIndex of previousTable.indexes) {\n if (!nextIndexes.has(previousIndex.name)) {\n destructiveChanges.push(\n `Index \"${table.name}.${previousIndex.name}\" was removed and requires a manual migration.`\n );\n }\n }\n\n const previousSearchIndexes = new Map(\n previousTable.searchIndexes.map((index) => [index.name, index])\n );\n const nextSearchIndexes = new Map(\n table.searchIndexes.map((index) => [index.name, index])\n );\n\n for (const searchIndex of table.searchIndexes) {\n const previousSearchIndex = previousSearchIndexes.get(searchIndex.name);\n if (!previousSearchIndex) {\n statements.push(renderCreateSearchIndexStatement(table.name, searchIndex));\n continue;\n }\n if (stableStringify(previousSearchIndex) !== stableStringify(searchIndex)) {\n destructiveChanges.push(\n `Search index \"${table.name}.${searchIndex.name}\" changed and requires a manual migration.`\n );\n }\n }\n\n for (const previousSearchIndex of previousTable.searchIndexes) {\n if (!nextSearchIndexes.has(previousSearchIndex.name)) {\n destructiveChanges.push(\n `Search index \"${table.name}.${previousSearchIndex.name}\" was removed and requires a manual migration.`\n );\n }\n }\n }\n\n for (const previousTable of previousSnapshot?.tables ?? []) {\n if (!nextTables.has(previousTable.name)) {\n destructiveChanges.push(\n `Table \"${previousTable.name}\" was removed and requires a manual migration.`\n );\n }\n }\n\n return {\n formatVersion: 3,\n plannerVersion: 2,\n previousHash: previousSnapshot?.hash ?? null,\n nextHash: nextSnapshot.hash,\n fromSchemaHash: previousSnapshot?.hash ?? null,\n toSchemaHash: nextSnapshot.hash,\n statements,\n warnings,\n destructiveChanges\n };\n}\n\nexport function renderMigrationSql(\n plan: SchemaMigrationPlan,\n options?: { title?: string }\n): string {\n const lines: string[] = [];\n\n lines.push(`-- ${options?.title ?? \"Syncore migration\"}`);\n lines.push(`-- format-version: ${plan.formatVersion}`);\n lines.push(`-- planner-version: ${plan.plannerVersion}`);\n lines.push(`-- previous: ${plan.previousHash ?? \"none\"}`);\n lines.push(`-- next: ${plan.nextHash}`);\n\n for (const warning of plan.warnings) {\n lines.push(`-- warning: ${warning}`);\n }\n\n if (plan.destructiveChanges.length > 0) {\n for (const destructiveChange of plan.destructiveChanges) {\n lines.push(`-- destructive: ${destructiveChange}`);\n }\n }\n\n if (plan.statements.length > 0) {\n lines.push(\"\");\n for (const statement of plan.statements) {\n lines.push(statement);\n }\n } else {\n lines.push(\"\");\n lines.push(\"-- no-op\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function parseSchemaSnapshot(source: string): SchemaSnapshot {\n const parsed = JSON.parse(source) as\n | SchemaSnapshot\n | {\n formatVersion: 2;\n plannerVersion: 1;\n runtimeVersion?: string;\n tables: Array<{\n name: string;\n displayName?: string;\n componentPath?: string;\n componentName?: string;\n validator: ValidatorDescription;\n indexes: Array<{ name: string; fields: string[] }>;\n searchIndexes: Array<{\n name: string;\n searchField: string;\n filterFields: string[];\n }>;\n }>;\n hash: string;\n }\n | {\n version: 1;\n tables: TableSnapshot[];\n hash: string;\n };\n\n if (\"formatVersion\" in parsed && parsed.formatVersion === 3) {\n if (\n parsed.plannerVersion !== 2 ||\n !Array.isArray(parsed.tables) ||\n typeof parsed.hash !== \"string\"\n ) {\n throw new Error(\"Invalid schema snapshot file.\");\n }\n return parsed;\n }\n\n if (\"formatVersion\" in parsed && parsed.formatVersion === 2) {\n return {\n formatVersion: 3,\n plannerVersion: 2,\n ...(parsed.runtimeVersion ? { runtimeVersion: parsed.runtimeVersion } : {}),\n tables: parsed.tables.map((table) => ({\n ...table,\n fieldPaths: extractFieldPaths(table.validator),\n fields: extractTopLevelFields(table.validator)\n })),\n hash: parsed.hash\n };\n }\n\n if (\n parsed.version !== 1 ||\n !Array.isArray(parsed.tables) ||\n typeof parsed.hash !== \"string\"\n ) {\n throw new Error(\"Invalid schema snapshot file.\");\n }\n return {\n formatVersion: 3,\n plannerVersion: 2,\n tables: parsed.tables.map((table) => ({\n ...table,\n fieldPaths: table.fieldPaths ?? extractFieldPaths(table.validator),\n fields: table.fields ?? extractTopLevelFields(table.validator)\n })),\n hash: parsed.hash\n };\n}\n\nexport function renderCreateTableStatement(tableName: string): string {\n return `\nCREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)} (\n _id TEXT PRIMARY KEY,\n _creationTime INTEGER NOT NULL,\n _json TEXT NOT NULL\n);`.trim();\n}\n\nexport function renderCreateIndexStatement(\n tableName: string,\n indexName: string,\n fields: string[]\n): string {\n const expressions = fields\n .map((field) => `json_extract(_json, '$.${field}')`)\n .join(\", \");\n return `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(\n `idx_${tableName}_${indexName}`\n )} ON ${quoteIdentifier(tableName)} (${expressions});`;\n}\n\nexport function renderCreateSearchIndexStatement(\n tableName: string,\n searchIndex: SearchIndexDefinition | TableSnapshot[\"searchIndexes\"][number]\n): string {\n return `CREATE VIRTUAL TABLE IF NOT EXISTS ${quoteIdentifier(\n searchIndexTableName(tableName, searchIndex.name)\n )} USING fts5(_id UNINDEXED, search_value);`;\n}\n\nexport function searchIndexTableName(tableName: string, indexName: string): string {\n return `fts_${tableName}_${indexName}`;\n}\n\nfunction createSchemaHash(\n value: Omit<SchemaSnapshot, \"hash\" | \"runtimeVersion\"> & {\n runtimeVersion?: string;\n }\n): string {\n return stableStringify(value);\n}\n\nfunction quoteIdentifier(identifier: string): string {\n return `\"${identifier.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortValue);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, sortValue(nested)])\n );\n }\n return value;\n}\n\nfunction extractFieldPaths(\n description: ValidatorDescription,\n prefix = \"\"\n): string[] {\n switch (description.kind) {\n case \"object\":\n return Object.entries(description.shape).flatMap(([key, entry]) => {\n const path = prefix ? `${prefix}.${key}` : key;\n const normalizedEntry = normalizeObjectFieldEntry(entry);\n const nested = extractFieldPaths(normalizedEntry.validator, path);\n return nested.length > 0 ? [path, ...nested] : [path];\n });\n case \"optional\":\n return extractFieldPaths(description.inner, prefix);\n case \"codec\":\n return extractFieldPaths(description.value, prefix);\n case \"union\":\n return [...new Set(description.members.flatMap((member) => extractFieldPaths(member, prefix)))];\n default:\n return [];\n }\n}\n\nfunction extractTopLevelFields(\n description: ValidatorDescription\n): TableFieldSnapshot[] {\n if (description.kind !== \"object\") {\n return [];\n }\n return Object.entries(description.shape)\n .map(([name, entry]) => {\n const normalizedEntry = normalizeObjectFieldEntry(entry);\n return {\n name,\n validator: normalizedEntry.validator,\n storage:\n normalizedEntry.validator.kind === \"codec\"\n ? normalizedEntry.validator.storage\n : normalizedEntry.validator,\n optional: normalizedEntry.optional\n };\n })\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction normalizeObjectFieldEntry(\n entry:\n | ValidatorDescription\n | {\n validator: ValidatorDescription;\n optional?: boolean;\n }\n): {\n validator: ValidatorDescription;\n optional: boolean;\n} {\n if (\"validator\" in entry) {\n return {\n validator: entry.validator,\n optional: entry.optional ?? false\n };\n }\n if (entry.kind === \"optional\") {\n return {\n validator: entry.inner,\n optional: true\n };\n }\n return {\n validator: entry,\n optional: false\n };\n}\n"],"mappings":"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"}
|