@rocicorp/zero 1.11.0-canary.41 → 1.11.0-canary.42
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/out/replicache/src/deleted-clients.d.ts +2 -2
- package/out/replicache/src/deleted-clients.js +2 -2
- package/out/replicache/src/deleted-clients.js.map +1 -1
- package/out/replicache/src/persist/client-group-gc.d.ts +1 -1
- package/out/replicache/src/persist/client-group-gc.js +1 -1
- package/out/replicache/src/persist/client-group-gc.js.map +1 -1
- package/out/replicache/src/replicache-options.d.ts +1 -1
- package/out/shared/src/browser-env.d.ts +1 -1
- package/out/shared/src/browser-env.js.map +1 -1
- package/out/shared/src/set-utils.d.ts +1 -1
- package/out/shared/src/set-utils.js +1 -1
- package/out/shared/src/set-utils.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +2 -0
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -2
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +17 -6
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/types/pg-patch.d.ts +21 -0
- package/out/zero-cache/src/types/pg-patch.d.ts.map +1 -0
- package/out/zero-cache/src/types/pg-patch.js +29 -0
- package/out/zero-cache/src/types/pg-patch.js.map +1 -0
- package/out/zero-client/src/client/connection-manager.js +1 -1
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/custom.d.ts +1 -1
- package/out/zero-client/src/client/custom.js +1 -1
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +6 -6
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +1 -1
- package/out/zero-client/src/client/zero.js +1 -1
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/inspect-up.d.ts +3 -3
- package/out/zero-protocol/src/inspect-up.d.ts.map +1 -1
- package/out/zero-protocol/src/inspect-up.js +2 -2
- package/out/zero-protocol/src/inspect-up.js.map +1 -1
- package/out/zero-react/src/use-zero-online.d.ts +1 -1
- package/out/zero-react/src/use-zero-online.js +1 -1
- package/out/zero-react/src/use-zero-online.js.map +1 -1
- package/out/zero-react/src/zero-provider.d.ts +1 -1
- package/out/zero-react/src/zero-provider.js +1 -1
- package/out/zero-react/src/zero-provider.js.map +1 -1
- package/out/zero-server/src/closable-db-transaction.d.ts +1 -1
- package/out/zero-server/src/closable-db-transaction.js +1 -1
- package/out/zero-server/src/closable-db-transaction.js.map +1 -1
- package/out/zero-server/src/custom.d.ts +1 -1
- package/out/zero-server/src/custom.js +1 -1
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/process-mutations.d.ts +3 -3
- package/out/zero-server/src/process-mutations.js +1 -1
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.d.ts +1 -1
- package/out/zero-server/src/push-processor.js +1 -1
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zero-server/src/queries/process-queries.d.ts +3 -3
- package/out/zero-server/src/queries/process-queries.js +2 -2
- package/out/zero-server/src/queries/process-queries.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +1 -1
- package/out/zero-server/src/schema.js +1 -1
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-server/src/zql-database.d.ts +2 -2
- package/out/zero-server/src/zql-database.js +2 -2
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/use-query.d.ts +2 -2
- package/out/zero-solid/src/use-query.js +1 -1
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-zero-online.d.ts +1 -1
- package/out/zero-solid/src/use-zero-online.js +1 -1
- package/out/zero-solid/src/use-zero-online.js.map +1 -1
- package/out/zero-solid/src/use-zero.d.ts +2 -2
- package/out/zero-solid/src/use-zero.js +2 -2
- package/out/zero-solid/src/use-zero.js.map +1 -1
- package/out/zql/src/ivm/change.d.ts +3 -3
- package/out/zql/src/ivm/change.js.map +1 -1
- package/out/zql/src/ivm/data.d.ts +1 -1
- package/out/zql/src/ivm/data.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/mutate/custom.d.ts +3 -3
- package/out/zql/src/mutate/custom.js.map +1 -1
- package/out/zql/src/query/create-builder.d.ts +1 -1
- package/out/zql/src/query/create-builder.d.ts.map +1 -1
- package/out/zql/src/query/create-builder.js +1 -1
- package/out/zql/src/query/create-builder.js.map +1 -1
- package/out/zql/src/query/named.d.ts +2 -2
- package/out/zql/src/query/named.js +2 -2
- package/out/zql/src/query/named.js.map +1 -1
- package/out/zql/src/query/query-impl.js +3 -3
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-internals.d.ts +5 -5
- package/out/zql/src/query/query-internals.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts +5 -5
- package/out/zql/src/query/query-transitions.js +2 -2
- package/out/zql/src/query/query-transitions.js.map +1 -1
- package/out/zql/src/query/query.d.ts +8 -8
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-processor.js","names":["#dbProvider","#logLevel","#logSink","#context","#processMutation","#dispatchMutation"],"sources":["../../../../zero-server/src/push-processor.ts"],"sourcesContent":["import {consoleLogSink, type LogLevel, type LogSink} from '@rocicorp/logger';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {getValueAtPath} from '../../shared/src/object-traversal.ts';\nimport type {MutateResponse} from '../../zero-protocol/src/mutate-server.ts';\nimport type {CustomMutation} from '../../zero-protocol/src/mutation.ts';\nimport {type MutationResponse} from '../../zero-protocol/src/push.ts';\nimport {\n type Database,\n type ExtractTransactionType,\n handleMutateRequest,\n type TransactFn,\n} from '../../zero-server/src/process-mutations.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {Transaction} from '../../zql/src/mutate/custom.ts';\nimport type {AnyMutatorRegistry} from '../../zql/src/mutate/mutator-registry.ts';\nimport {isMutator} from '../../zql/src/mutate/mutator.ts';\nimport type {CustomMutatorDefs} from './custom.ts';\n\nexport const separatorRe = /[.|]/;\n\nexport class PushProcessor<\n _S extends Schema,\n D extends Database<ExtractTransactionType<D>>,\n MD extends AnyMutatorRegistry | CustomMutatorDefs<ExtractTransactionType<D>>,\n C = undefined,\n> {\n readonly #dbProvider: D;\n readonly #logLevel: LogLevel;\n readonly #logSink: LogSink;\n readonly #context: C;\n\n /**\n * @param logSink Destination for this processor's log output. When omitted,\n * logs are written with `console.log`/`info`/`warn`/`error` etc. Provide a\n * custom {@
|
|
1
|
+
{"version":3,"file":"push-processor.js","names":["#dbProvider","#logLevel","#logSink","#context","#processMutation","#dispatchMutation"],"sources":["../../../../zero-server/src/push-processor.ts"],"sourcesContent":["import {consoleLogSink, type LogLevel, type LogSink} from '@rocicorp/logger';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {getValueAtPath} from '../../shared/src/object-traversal.ts';\nimport type {MutateResponse} from '../../zero-protocol/src/mutate-server.ts';\nimport type {CustomMutation} from '../../zero-protocol/src/mutation.ts';\nimport {type MutationResponse} from '../../zero-protocol/src/push.ts';\nimport {\n type Database,\n type ExtractTransactionType,\n handleMutateRequest,\n type TransactFn,\n} from '../../zero-server/src/process-mutations.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {Transaction} from '../../zql/src/mutate/custom.ts';\nimport type {AnyMutatorRegistry} from '../../zql/src/mutate/mutator-registry.ts';\nimport {isMutator} from '../../zql/src/mutate/mutator.ts';\nimport type {CustomMutatorDefs} from './custom.ts';\n\nexport const separatorRe = /[.|]/;\n\nexport class PushProcessor<\n _S extends Schema,\n D extends Database<ExtractTransactionType<D>>,\n MD extends AnyMutatorRegistry | CustomMutatorDefs<ExtractTransactionType<D>>,\n C = undefined,\n> {\n readonly #dbProvider: D;\n readonly #logLevel: LogLevel;\n readonly #logSink: LogSink;\n readonly #context: C;\n\n /**\n * @param logSink Destination for this processor's log output. When omitted,\n * logs are written with `console.log`/`info`/`warn`/`error` etc. Provide a\n * custom {@link LogSink} to redirect them into the application's own\n * logging pipeline.\n */\n constructor(\n dbProvider: D,\n context?: C,\n logLevel: LogLevel = 'info',\n logSink: LogSink = consoleLogSink,\n ) {\n this.#dbProvider = dbProvider;\n this.#context = context as C;\n this.#logLevel = logLevel;\n this.#logSink = logSink;\n }\n\n /**\n * Processes a push request from zero-cache.\n * This function will parse the request, check the protocol version, and process each mutation in the request.\n * - If a mutation is out of order: processing will stop and an error will be returned. The zero client will retry the mutation.\n * - If a mutation has already been processed: it will be skipped and the processing will continue.\n * - If a mutation receives an application error: it will be skipped, the error will be returned to the client, and processing will continue.\n *\n * @param mutators the custom mutators for the application\n * @param queryString the query string from the request sent by zero-cache. This will include zero's postgres schema name and appID.\n * @param body the body of the request sent by zero-cache as a JSON object.\n */\n process(\n mutators: MD,\n queryString: URLSearchParams | Record<string, string>,\n body: ReadonlyJSONValue,\n ): Promise<MutateResponse>;\n\n /**\n * This override gets the query string and the body from a Request object.\n *\n * @param mutators the custom mutators for the application\n * @param request A `Request` object.\n */\n process(mutators: MD, request: Request): Promise<MutateResponse>;\n process(\n mutators: MD,\n queryOrQueryString: Request | URLSearchParams | Record<string, string>,\n body?: ReadonlyJSONValue,\n ): Promise<MutateResponse> {\n if (queryOrQueryString instanceof Request) {\n return handleMutateRequest(\n this.#dbProvider,\n (transact, mutation) =>\n this.#processMutation(mutators, transact, mutation),\n queryOrQueryString,\n this.#logLevel,\n this.#logSink,\n );\n }\n return handleMutateRequest(\n this.#dbProvider,\n (transact, mutation) =>\n this.#processMutation(mutators, transact, mutation),\n queryOrQueryString,\n must(body, 'body is required when using query params directly'),\n this.#logLevel,\n this.#logSink,\n );\n }\n\n #processMutation(\n mutators: MD,\n transact: TransactFn<D>,\n _mutation: CustomMutation,\n ): Promise<MutationResponse> {\n return transact((tx, name, args) =>\n this.#dispatchMutation(mutators, tx, name, args),\n );\n }\n\n #dispatchMutation(\n mutators: MD,\n dbTx: ExtractTransactionType<D>,\n key: string,\n args: ReadonlyJSONValue | undefined,\n ): Promise<ReadonlyJSONValue | void> {\n // Legacy mutators used | as a separator, new mutators use .\n const mutator = getValueAtPath(mutators, key, separatorRe);\n assert(typeof mutator === 'function', `could not find mutator ${key}`);\n if (isMutator(mutator)) {\n return mutator.fn({\n args,\n ctx: this.#context,\n tx: dbTx as Transaction<Schema, unknown>,\n });\n }\n return mutator(dbTx, args);\n }\n}\n"],"mappings":";;;;;;;;AAoBA,IAAa,cAAc;AAE3B,IAAa,gBAAb,MAKE;CACA;CACA;CACA;CACA;;;;;;;CAQA,YACE,YACA,SACA,WAAqB,QACrB,UAAmB,gBACnB;EACA,KAAKA,cAAc;EACnB,KAAKG,WAAW;EAChB,KAAKF,YAAY;EACjB,KAAKC,WAAW;CAClB;CA0BA,QACE,UACA,oBACA,MACyB;EACzB,IAAI,8BAA8B,SAChC,OAAO,oBACL,KAAKF,cACJ,UAAU,aACT,KAAKI,iBAAiB,UAAU,UAAU,QAAQ,GACpD,oBACA,KAAKH,WACL,KAAKC,QACP;EAEF,OAAO,oBACL,KAAKF,cACJ,UAAU,aACT,KAAKI,iBAAiB,UAAU,UAAU,QAAQ,GACpD,oBACA,KAAK,MAAM,mDAAmD,GAC9D,KAAKH,WACL,KAAKC,QACP;CACF;CAEA,iBACE,UACA,UACA,WAC2B;EAC3B,OAAO,UAAU,IAAI,MAAM,SACzB,KAAKG,kBAAkB,UAAU,IAAI,MAAM,IAAI,CACjD;CACF;CAEA,kBACE,UACA,MACA,KACA,MACmC;EAEnC,MAAM,UAAU,eAAe,UAAU,KAAK,WAAW;EACzD,OAAO,OAAO,YAAY,YAAY,0BAA0B,KAAK;EACrE,IAAI,UAAU,OAAO,GACnB,OAAO,QAAQ,GAAG;GAChB;GACA,KAAK,KAAKF;GACV,IAAI;EACN,CAAC;EAEH,OAAO,QAAQ,MAAM,IAAI;CAC3B;AACF"}
|
|
@@ -11,7 +11,7 @@ import type { AnyQuery } from '../../../zql/src/query/query.ts';
|
|
|
11
11
|
* This function will call `cb` in parallel for each query found in the request.
|
|
12
12
|
*
|
|
13
13
|
* If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.
|
|
14
|
-
* @deprecated Use {@
|
|
14
|
+
* @deprecated Use {@link handleQueryRequest} instead.
|
|
15
15
|
*/
|
|
16
16
|
export declare function handleGetQueriesRequest<S extends Schema>(cb: (name: string, args: readonly ReadonlyJSONValue[]) => MaybePromise<{
|
|
17
17
|
query: AnyQuery;
|
|
@@ -23,13 +23,13 @@ export declare function handleGetQueriesRequest<S extends Schema>(cb: (name: str
|
|
|
23
23
|
* This function will call `cb` in parallel for each query found in the request.
|
|
24
24
|
*
|
|
25
25
|
* If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.
|
|
26
|
-
* @deprecated Use {@
|
|
26
|
+
* @deprecated Use {@link handleQueryRequest} instead.
|
|
27
27
|
*/
|
|
28
28
|
export declare function handleTransformRequest<S extends Schema>(cb: (name: string, args: readonly ReadonlyJSONValue[]) => MaybePromise<{
|
|
29
29
|
query: AnyQuery;
|
|
30
30
|
} | AnyQuery>, schema: S, requestOrJsonBody: Request | ReadonlyJSONValue, logLevel?: LogLevel): Promise<QueryResponse>;
|
|
31
31
|
/**
|
|
32
|
-
* Parsed query params accepted by {@
|
|
32
|
+
* Parsed query params accepted by {@link handleQueryRequest} when the
|
|
33
33
|
* incoming request URL has already been handled by your framework.
|
|
34
34
|
*/
|
|
35
35
|
export type QuerySearchParams = URLSearchParams | Record<string, string>;
|
|
@@ -18,7 +18,7 @@ import { createLogContext } from "../logging.js";
|
|
|
18
18
|
* This function will call `cb` in parallel for each query found in the request.
|
|
19
19
|
*
|
|
20
20
|
* If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.
|
|
21
|
-
* @deprecated Use {@
|
|
21
|
+
* @deprecated Use {@link handleQueryRequest} instead.
|
|
22
22
|
*/
|
|
23
23
|
function handleGetQueriesRequest(cb, schema, requestOrJsonBody, logLevel = "info") {
|
|
24
24
|
return transform(normalizeLegacyQueryRequestArgs(cb, schema, requestOrJsonBody, logLevel), "getQueries");
|
|
@@ -30,7 +30,7 @@ function handleGetQueriesRequest(cb, schema, requestOrJsonBody, logLevel = "info
|
|
|
30
30
|
* This function will call `cb` in parallel for each query found in the request.
|
|
31
31
|
*
|
|
32
32
|
* If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.
|
|
33
|
-
* @deprecated Use {@
|
|
33
|
+
* @deprecated Use {@link handleQueryRequest} instead.
|
|
34
34
|
*/
|
|
35
35
|
function handleTransformRequest(cb, schema, requestOrJsonBody, logLevel = "info") {
|
|
36
36
|
return transform(normalizeLegacyQueryRequestArgs(cb, schema, requestOrJsonBody, logLevel), "transform");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-queries.js","names":[],"sources":["../../../../../zero-server/src/queries/process-queries.ts"],"sourcesContent":["import type {LogLevel} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {getErrorDetails, getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {mapAST} from '../../../zero-protocol/src/ast.ts';\nimport {\n transformRequestMessageSchema,\n type TransformRequestMessage,\n type TransformResponseBody,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport type {QueryResponse} from '../../../zero-protocol/src/query-server.ts';\nimport {clientToServer} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {QueryParseError} from '../../../zql/src/query/error.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {AnyQuery} from '../../../zql/src/query/query.ts';\nimport {createLogContext} from '../logging.ts';\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@linkcode handleQueryRequest} instead.\n */\nexport function handleGetQueriesRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n return transform(\n normalizeLegacyQueryRequestArgs(cb, schema, requestOrJsonBody, logLevel),\n 'getQueries',\n );\n}\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@linkcode handleQueryRequest} instead.\n */\nexport function handleTransformRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n return transform(\n normalizeLegacyQueryRequestArgs(cb, schema, requestOrJsonBody, logLevel),\n 'transform',\n );\n}\n\n/**\n * Parsed query params accepted by {@linkcode handleQueryRequest} when the\n * incoming request URL has already been handled by your framework.\n */\nexport type QuerySearchParams = URLSearchParams | Record<string, string>;\n\nexport type HandleQueryRequestArgs<S extends Schema> = {\n /** Callback that transforms each requested query into a `Query`. */\n handler: QueryRequestHandler;\n /** Schema used when building the returned ASTs. */\n schema: S;\n /**\n * Authenticated user ID. Null or undefined means the user is logged out.\n */\n userID: string | null | undefined;\n /** Optional log level for request parsing and execution. */\n logLevel?: LogLevel | undefined;\n} & (\n | {\n /** Fetch request containing the `/query` JSON body. */\n request: Request;\n }\n | {\n /** Parsed query params from the `/query` request URL. */\n query: QuerySearchParams;\n /** Parsed JSON body from the `/query` request. */\n body: ReadonlyJSONValue;\n }\n);\n\ntype NormalizedQueryRequestArgs<S extends Schema> = {\n readonly schema: S;\n readonly handler: LegacyQueryRequestHandler;\n // Note: semantics of undefined differ from HandleQueryRequestArgs.userID.\n // Here, undefined means the app didn't provide a user ID - we do not know if\n // the user is logged in or not. This is legacy behavior needed to support\n // deprecated signatures of handleQueryRequest which did not receive userID\n // from app.\n readonly userID: string | null | undefined;\n readonly logLevel: LogLevel;\n} & (\n | {\n readonly type: 'request';\n readonly request: Request;\n }\n | {\n readonly type: 'body';\n readonly jsonBody: ReadonlyJSONValue;\n }\n);\n\n/**\n * Process a `/query` request from a Fetch `Request`.\n */\nexport function handleQueryRequest<S extends Schema>(\n input: HandleQueryRequestArgs<S>,\n): Promise<QueryResponse>;\n\n/**\n * @deprecated Pass a single object instead:\n * `handleQueryRequest({handler, schema, request, userID, logLevel})`.\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: QueryRequestHandler,\n schema: S,\n request: Request,\n logLevel?: LogLevel,\n): Promise<QueryResponse>;\n\n/**\n * @deprecated Pass a single object instead:\n * `handleQueryRequest({handler, schema, body, userID, logLevel})`.\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: QueryRequestHandler,\n schema: S,\n jsonBody: ReadonlyJSONValue,\n logLevel?: LogLevel,\n): Promise<QueryResponse>;\n\nexport function handleQueryRequest<S extends Schema>(\n inputOrTransformQuery: HandleQueryRequestArgs<S> | QueryRequestHandler,\n schema?: S,\n requestOrJsonBody?: Request | ReadonlyJSONValue,\n logLevel?: LogLevel,\n): Promise<QueryResponse> {\n const normalized =\n typeof inputOrTransformQuery === 'object' &&\n 'handler' in inputOrTransformQuery\n ? normalizeQueryRequestInput(inputOrTransformQuery)\n : normalizeLegacyQueryRequestArgs(\n wrapQueryRequestHandler(inputOrTransformQuery),\n schema,\n requestOrJsonBody,\n logLevel,\n );\n\n return transform(normalized, 'query');\n}\n\nfunction normalizeQueryRequestInput<S extends Schema>(\n input: HandleQueryRequestArgs<S>,\n): NormalizedQueryRequestArgs<S> {\n return 'request' in input\n ? normalizeLegacyQueryRequestArgs(\n wrapQueryRequestHandler(input.handler),\n input.schema,\n input.request,\n input.logLevel,\n input.userID ?? null,\n )\n : normalizeLegacyQueryRequestArgs(\n wrapQueryRequestHandler(input.handler),\n input.schema,\n input.body,\n input.logLevel,\n input.userID ?? null,\n );\n}\n\nfunction normalizeLegacyQueryRequestArgs<S extends Schema>(\n handler: LegacyQueryRequestHandler,\n schema: S | undefined,\n requestOrJsonBody: Request | ReadonlyJSONValue | undefined,\n logLevel: LogLevel | undefined,\n userID?: string | null,\n): NormalizedQueryRequestArgs<S> {\n assert(\n typeof schema !== 'undefined',\n 'Schema must be provided when using handleQueryRequest',\n );\n\n if (requestOrJsonBody instanceof Request) {\n return {\n type: 'request',\n handler,\n schema,\n request: requestOrJsonBody,\n userID,\n logLevel: logLevel ?? 'info',\n };\n }\n\n assert(\n typeof requestOrJsonBody !== 'undefined',\n 'JSON body cannot be undefined',\n );\n\n return {\n type: 'body',\n handler,\n schema,\n jsonBody: requestOrJsonBody,\n userID,\n logLevel: logLevel ?? 'info',\n };\n}\n\nasync function transform<S extends Schema>(\n args: NormalizedQueryRequestArgs<S>,\n apiName: 'query' | 'transform' | 'getQueries',\n): Promise<QueryResponse> {\n const lc = createLogContext(args.logLevel).withContext('TransformRequest');\n let parsed: TransformRequestMessage;\n let queryIDs: string[] = [];\n try {\n let body: ReadonlyJSONValue;\n if (args.type === 'request') {\n body = await args.request.json();\n } else {\n body = args.jsonBody;\n }\n\n // Strip, so a newer zero-cache can add fields to the request.\n parsed = v.parse(body, transformRequestMessageSchema, 'strip');\n\n queryIDs = parsed[1].map(r => r.id);\n } catch (error) {\n lc.error?.(`Failed to parse ${apiName} request`, error);\n\n const message = `Failed to parse ${apiName} request: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const nameMapper = clientToServer(args.schema.tables);\n\n const responses: TransformResponseBody = await Promise.all(\n parsed[1].map(async req => {\n let finalQuery: AnyQuery;\n try {\n const result = await args.handler(req.name, req.args);\n finalQuery = 'query' in result ? result.query : result;\n } catch (error) {\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return {\n error: error instanceof QueryParseError ? 'parse' : 'app',\n id: req.id,\n name: req.name,\n message,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const q = asQueryInternals(finalQuery);\n const ast = mapAST(q.ast, nameMapper);\n\n return {\n id: req.id,\n name: req.name,\n ast,\n };\n } catch (error) {\n lc.error?.('Failed to map AST', error);\n throw error;\n }\n }),\n );\n\n return {\n kind: 'QueryResponse',\n queries: responses,\n ...(typeof args.userID !== 'undefined' ? {userID: args.userID} : {}),\n } as const satisfies QueryResponse;\n } catch (e) {\n const message = getErrorMessage(e);\n const details = getErrorDetails(e);\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Internal,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n };\n }\n}\n\n/**\n * A function that transforms a query by name and arguments into a Query object.\n *\n * @param name - The name of the query (can be dot-separated for nested queries)\n * @param args - The arguments to pass to the query (can be undefined)\n * @returns A Query object\n */\nexport type QueryRequestHandler = (\n name: string,\n args: ReadonlyJSONValue | undefined,\n) => AnyQuery;\n\n/** @deprecated Use `QueryRequestHandler` instead. */\nexport type TransformQueryFunction = QueryRequestHandler;\n\nexport type LegacyQueryRequestHandler = (\n name: string,\n args: readonly ReadonlyJSONValue[],\n) => MaybePromise<{query: AnyQuery} | AnyQuery>;\n\nfunction wrapQueryRequestHandler(\n handler: QueryRequestHandler,\n): LegacyQueryRequestHandler {\n return (name, args) => handler(name, args[0]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"process-queries.js","names":[],"sources":["../../../../../zero-server/src/queries/process-queries.ts"],"sourcesContent":["import type {LogLevel} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {getErrorDetails, getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {mapAST} from '../../../zero-protocol/src/ast.ts';\nimport {\n transformRequestMessageSchema,\n type TransformRequestMessage,\n type TransformResponseBody,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport type {QueryResponse} from '../../../zero-protocol/src/query-server.ts';\nimport {clientToServer} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {QueryParseError} from '../../../zql/src/query/error.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {AnyQuery} from '../../../zql/src/query/query.ts';\nimport {createLogContext} from '../logging.ts';\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@link handleQueryRequest} instead.\n */\nexport function handleGetQueriesRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n return transform(\n normalizeLegacyQueryRequestArgs(cb, schema, requestOrJsonBody, logLevel),\n 'getQueries',\n );\n}\n\n/**\n * Invokes the callback `cb` for each query in the request or JSON body.\n * The callback should return a Query or Promise<Query> that is the transformed result.\n *\n * This function will call `cb` in parallel for each query found in the request.\n *\n * If you need to limit concurrency, you can use a library like `p-limit` to wrap the `cb` function.\n * @deprecated Use {@link handleQueryRequest} instead.\n */\nexport function handleTransformRequest<S extends Schema>(\n cb: (\n name: string,\n args: readonly ReadonlyJSONValue[],\n ) => MaybePromise<{query: AnyQuery} | AnyQuery>,\n schema: S,\n requestOrJsonBody: Request | ReadonlyJSONValue,\n logLevel: LogLevel = 'info',\n): Promise<QueryResponse> {\n return transform(\n normalizeLegacyQueryRequestArgs(cb, schema, requestOrJsonBody, logLevel),\n 'transform',\n );\n}\n\n/**\n * Parsed query params accepted by {@link handleQueryRequest} when the\n * incoming request URL has already been handled by your framework.\n */\nexport type QuerySearchParams = URLSearchParams | Record<string, string>;\n\nexport type HandleQueryRequestArgs<S extends Schema> = {\n /** Callback that transforms each requested query into a `Query`. */\n handler: QueryRequestHandler;\n /** Schema used when building the returned ASTs. */\n schema: S;\n /**\n * Authenticated user ID. Null or undefined means the user is logged out.\n */\n userID: string | null | undefined;\n /** Optional log level for request parsing and execution. */\n logLevel?: LogLevel | undefined;\n} & (\n | {\n /** Fetch request containing the `/query` JSON body. */\n request: Request;\n }\n | {\n /** Parsed query params from the `/query` request URL. */\n query: QuerySearchParams;\n /** Parsed JSON body from the `/query` request. */\n body: ReadonlyJSONValue;\n }\n);\n\ntype NormalizedQueryRequestArgs<S extends Schema> = {\n readonly schema: S;\n readonly handler: LegacyQueryRequestHandler;\n // Note: semantics of undefined differ from HandleQueryRequestArgs.userID.\n // Here, undefined means the app didn't provide a user ID - we do not know if\n // the user is logged in or not. This is legacy behavior needed to support\n // deprecated signatures of handleQueryRequest which did not receive userID\n // from app.\n readonly userID: string | null | undefined;\n readonly logLevel: LogLevel;\n} & (\n | {\n readonly type: 'request';\n readonly request: Request;\n }\n | {\n readonly type: 'body';\n readonly jsonBody: ReadonlyJSONValue;\n }\n);\n\n/**\n * Process a `/query` request from a Fetch `Request`.\n */\nexport function handleQueryRequest<S extends Schema>(\n input: HandleQueryRequestArgs<S>,\n): Promise<QueryResponse>;\n\n/**\n * @deprecated Pass a single object instead:\n * `handleQueryRequest({handler, schema, request, userID, logLevel})`.\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: QueryRequestHandler,\n schema: S,\n request: Request,\n logLevel?: LogLevel,\n): Promise<QueryResponse>;\n\n/**\n * @deprecated Pass a single object instead:\n * `handleQueryRequest({handler, schema, body, userID, logLevel})`.\n */\nexport function handleQueryRequest<S extends Schema>(\n transformQuery: QueryRequestHandler,\n schema: S,\n jsonBody: ReadonlyJSONValue,\n logLevel?: LogLevel,\n): Promise<QueryResponse>;\n\nexport function handleQueryRequest<S extends Schema>(\n inputOrTransformQuery: HandleQueryRequestArgs<S> | QueryRequestHandler,\n schema?: S,\n requestOrJsonBody?: Request | ReadonlyJSONValue,\n logLevel?: LogLevel,\n): Promise<QueryResponse> {\n const normalized =\n typeof inputOrTransformQuery === 'object' &&\n 'handler' in inputOrTransformQuery\n ? normalizeQueryRequestInput(inputOrTransformQuery)\n : normalizeLegacyQueryRequestArgs(\n wrapQueryRequestHandler(inputOrTransformQuery),\n schema,\n requestOrJsonBody,\n logLevel,\n );\n\n return transform(normalized, 'query');\n}\n\nfunction normalizeQueryRequestInput<S extends Schema>(\n input: HandleQueryRequestArgs<S>,\n): NormalizedQueryRequestArgs<S> {\n return 'request' in input\n ? normalizeLegacyQueryRequestArgs(\n wrapQueryRequestHandler(input.handler),\n input.schema,\n input.request,\n input.logLevel,\n input.userID ?? null,\n )\n : normalizeLegacyQueryRequestArgs(\n wrapQueryRequestHandler(input.handler),\n input.schema,\n input.body,\n input.logLevel,\n input.userID ?? null,\n );\n}\n\nfunction normalizeLegacyQueryRequestArgs<S extends Schema>(\n handler: LegacyQueryRequestHandler,\n schema: S | undefined,\n requestOrJsonBody: Request | ReadonlyJSONValue | undefined,\n logLevel: LogLevel | undefined,\n userID?: string | null,\n): NormalizedQueryRequestArgs<S> {\n assert(\n typeof schema !== 'undefined',\n 'Schema must be provided when using handleQueryRequest',\n );\n\n if (requestOrJsonBody instanceof Request) {\n return {\n type: 'request',\n handler,\n schema,\n request: requestOrJsonBody,\n userID,\n logLevel: logLevel ?? 'info',\n };\n }\n\n assert(\n typeof requestOrJsonBody !== 'undefined',\n 'JSON body cannot be undefined',\n );\n\n return {\n type: 'body',\n handler,\n schema,\n jsonBody: requestOrJsonBody,\n userID,\n logLevel: logLevel ?? 'info',\n };\n}\n\nasync function transform<S extends Schema>(\n args: NormalizedQueryRequestArgs<S>,\n apiName: 'query' | 'transform' | 'getQueries',\n): Promise<QueryResponse> {\n const lc = createLogContext(args.logLevel).withContext('TransformRequest');\n let parsed: TransformRequestMessage;\n let queryIDs: string[] = [];\n try {\n let body: ReadonlyJSONValue;\n if (args.type === 'request') {\n body = await args.request.json();\n } else {\n body = args.jsonBody;\n }\n\n // Strip, so a newer zero-cache can add fields to the request.\n parsed = v.parse(body, transformRequestMessageSchema, 'strip');\n\n queryIDs = parsed[1].map(r => r.id);\n } catch (error) {\n lc.error?.(`Failed to parse ${apiName} request`, error);\n\n const message = `Failed to parse ${apiName} request: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const nameMapper = clientToServer(args.schema.tables);\n\n const responses: TransformResponseBody = await Promise.all(\n parsed[1].map(async req => {\n let finalQuery: AnyQuery;\n try {\n const result = await args.handler(req.name, req.args);\n finalQuery = 'query' in result ? result.query : result;\n } catch (error) {\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return {\n error: error instanceof QueryParseError ? 'parse' : 'app',\n id: req.id,\n name: req.name,\n message,\n ...(details ? {details} : {}),\n };\n }\n\n try {\n const q = asQueryInternals(finalQuery);\n const ast = mapAST(q.ast, nameMapper);\n\n return {\n id: req.id,\n name: req.name,\n ast,\n };\n } catch (error) {\n lc.error?.('Failed to map AST', error);\n throw error;\n }\n }),\n );\n\n return {\n kind: 'QueryResponse',\n queries: responses,\n ...(typeof args.userID !== 'undefined' ? {userID: args.userID} : {}),\n } as const satisfies QueryResponse;\n } catch (e) {\n const message = getErrorMessage(e);\n const details = getErrorDetails(e);\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Internal,\n message,\n queryIDs,\n ...(details ? {details} : {}),\n };\n }\n}\n\n/**\n * A function that transforms a query by name and arguments into a Query object.\n *\n * @param name - The name of the query (can be dot-separated for nested queries)\n * @param args - The arguments to pass to the query (can be undefined)\n * @returns A Query object\n */\nexport type QueryRequestHandler = (\n name: string,\n args: ReadonlyJSONValue | undefined,\n) => AnyQuery;\n\n/** @deprecated Use `QueryRequestHandler` instead. */\nexport type TransformQueryFunction = QueryRequestHandler;\n\nexport type LegacyQueryRequestHandler = (\n name: string,\n args: readonly ReadonlyJSONValue[],\n) => MaybePromise<{query: AnyQuery} | AnyQuery>;\n\nfunction wrapQueryRequestHandler(\n handler: QueryRequestHandler,\n): LegacyQueryRequestHandler {\n return (name, args) => handler(name, args[0]);\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import type { ServerSchema } from '../../zero-types/src/server-schema.ts';
|
|
|
3
3
|
import type { Queryable } from '../../zql/src/mutate/custom.ts';
|
|
4
4
|
export declare function getServerSchema<S extends Schema>(queryable: Queryable, schema: S): Promise<ServerSchema>;
|
|
5
5
|
/**
|
|
6
|
-
* The query behind {@
|
|
6
|
+
* The query behind {@link getServerSchema}, exported so that it can be
|
|
7
7
|
* diffed against the `information_schema` query it replaced.
|
|
8
8
|
*/
|
|
9
9
|
export declare function serverSchemaQuery(schemaTablePairs: readonly (readonly [string, string])[]): {
|
|
@@ -43,7 +43,7 @@ async function getServerSchema(queryable, schema) {
|
|
|
43
43
|
return serverSchema;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* The query behind {@
|
|
46
|
+
* The query behind {@link getServerSchema}, exported so that it can be
|
|
47
47
|
* diffed against the `information_schema` query it replaced.
|
|
48
48
|
*/
|
|
49
49
|
function serverSchemaQuery(schemaTablePairs) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../../zero-server/src/schema.ts"],"sourcesContent":["import {assert} from '../../shared/src/asserts.ts';\nimport type {Enum} from '../../shared/src/enum.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport * as PostgresTypeClass from '../../zero-cache/src/db/postgres-type-class-enum.ts';\nimport {dataTypeToZqlValueType} from '../../zero-cache/src/types/pg-data-type.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {ServerSchema} from '../../zero-types/src/server-schema.ts';\nimport type {Queryable} from '../../zql/src/mutate/custom.ts';\n\ntype PostgresTypeClass = Enum<typeof PostgresTypeClass>;\n\ntype ServerSchemaRow = {\n schema: string;\n table: string;\n column: string;\n dataType: string;\n length: string | null;\n precision: string | null;\n scale: string | null;\n typtype: PostgresTypeClass;\n typename: string;\n elemTyptype: PostgresTypeClass | null;\n elemTypname: string | null;\n};\n\nexport async function getServerSchema<S extends Schema>(\n queryable: Queryable,\n schema: S,\n): Promise<ServerSchema> {\n const schemaTablePairs: [string, string][] = Object.values(schema.tables).map(\n ({name, serverName}) => {\n let schemaTablePair: [string, string] = ['public', serverName ?? name];\n if (serverName) {\n const firstPeriod = serverName.indexOf('.');\n if (firstPeriod > -1) {\n schemaTablePair = [\n serverName.substring(0, firstPeriod),\n serverName.substring(firstPeriod + 1, serverName.length),\n ];\n }\n }\n return schemaTablePair;\n },\n );\n\n if (schemaTablePairs.length === 0) {\n return {}; // No pairs to query for\n }\n\n const {text, values} = serverSchemaQuery(schemaTablePairs);\n const results: Iterable<ServerSchemaRow> = (await queryable.query(\n text,\n values,\n )) as Iterable<ServerSchemaRow>;\n\n const serverSchema: ServerSchema = {};\n\n for (const row of results) {\n const tableName =\n row.schema === 'public' ? row.table : `${row.schema}.${row.table}`;\n let tableSchema = serverSchema[tableName];\n if (!tableSchema) {\n tableSchema = {};\n serverSchema[tableName] = tableSchema;\n }\n const isArray = row.elemTyptype !== null;\n const isEnum = (row.elemTyptype ?? row.typtype) === PostgresTypeClass.Enum;\n let type = row.dataType.toLowerCase();\n if (isArray) {\n type = must(\n row.elemTypname,\n `Array column \"${row.column}\" in table \"${tableName}\" is missing element type name`,\n );\n } else if (isEnum) {\n type = row.typename;\n } else if (\n type === 'user-defined' &&\n dataTypeToZqlValueType(row.typename, false, false) !== undefined\n ) {\n type = row.typename;\n }\n if (\n (type === 'bpchar' ||\n type === 'character' ||\n type === 'character varying' ||\n type === 'varchar') &&\n row.length\n ) {\n type = `${type}(${row.length})`;\n } else if (type === 'character') {\n // Without a length `character` means `character(1)`, so, like Postgres's\n // own format_type, name an unbounded `bpchar` column `bpchar`.\n type = 'bpchar';\n }\n if (\n (type === 'numeric' || type === 'decimal') &&\n row.precision !== null &&\n row.scale !== null\n ) {\n type = `${type}(${row.precision}, ${row.scale})`;\n }\n tableSchema[row.column] = {\n type,\n isEnum,\n isArray,\n };\n }\n\n const errors = checkSchemasAreCompatible(schema, serverSchema);\n assert(errors.length === 0, () => makeSchemaIncompatibleErrorMessage(errors));\n\n return serverSchema;\n}\n\n/**\n * The query behind {@linkcode getServerSchema}, exported so that it can be\n * diffed against the `information_schema` query it replaced.\n */\nexport function serverSchemaQuery(\n schemaTablePairs: readonly (readonly [string, string])[],\n): {text: string; values: unknown[]} {\n // Cast all inputs to text and all outputs to text to avoid\n // any conversions customer's DBTransaction impl has on other types.\n const inClause = sql.join(\n schemaTablePairs.map(\n ([schema, table]) => sql`(${schema}::text, ${table}::text)`,\n ),\n ',',\n );\n // Read pg_catalog directly instead of information_schema.columns. The view\n // derives every column it returns through the information_schema._pg_*\n // functions and a per column privilege check, which is several times slower\n // than the query below. Server processes run this on the first transaction\n // they serve, so serverless deployments pay for it on every cold start.\n //\n // The expressions below reproduce the parts of the view's definition that we\n // select, down to the int4 wire types of length, precision and scale: the\n // _pg_char_max_length / _pg_numeric_precision / _pg_numeric_scale arithmetic,\n // the single level domain unwrap that data_type and udt_name perform (`ut`),\n // and the view's own relkind, attnum and privilege filters. pg_type is joined\n // by OID rather than by udt_name, which additionally avoids duplicate rows\n // when two schemas declare a type with the same name. The types those\n // functions special case are named through regtype, which the parser resolves\n // while planning.\n const query = sql`\n SELECT\n n.nspname::text AS schema,\n cl.relname::text AS table,\n a.attname::text AS column,\n CASE\n WHEN ut.typelem <> 0 AND ut.typlen = -1 THEN 'ARRAY'\n WHEN nut.nspname = 'pg_catalog' THEN pg_catalog.format_type(ut.oid, NULL)\n ELSE 'USER-DEFINED'\n END::text AS \"dataType\",\n CASE\n WHEN tm.typmod = -1 THEN NULL\n WHEN ut.oid IN ('pg_catalog.bpchar'::regtype, 'pg_catalog.varchar'::regtype) THEN tm.typmod - 4\n WHEN ut.oid IN ('pg_catalog.bit'::regtype, 'pg_catalog.varbit'::regtype) THEN tm.typmod\n END AS length,\n CASE ut.oid\n WHEN 'pg_catalog.int2'::regtype THEN 16\n WHEN 'pg_catalog.int4'::regtype THEN 32\n WHEN 'pg_catalog.int8'::regtype THEN 64\n WHEN 'pg_catalog.float4'::regtype THEN 24\n WHEN 'pg_catalog.float8'::regtype THEN 53\n WHEN 'pg_catalog.numeric'::regtype THEN CASE WHEN tm.typmod = -1 THEN NULL ELSE ((tm.typmod - 4) >> 16) & 65535 END\n END AS precision,\n CASE\n WHEN ut.oid IN ('pg_catalog.int2'::regtype, 'pg_catalog.int4'::regtype, 'pg_catalog.int8'::regtype) THEN 0\n WHEN ut.oid = 'pg_catalog.numeric'::regtype AND tm.typmod <> -1 THEN (tm.typmod - 4) & 65535\n END AS scale,\n ut.typtype::text AS typtype,\n ut.typname::text AS typename,\n CASE WHEN ut.typelem <> 0 THEN et.typtype::text ELSE NULL END AS \"elemTyptype\",\n CASE WHEN ut.typelem <> 0 THEN et.typname::text ELSE NULL END AS \"elemTypname\"\n FROM\n pg_catalog.pg_attribute a\n JOIN\n pg_catalog.pg_class cl ON cl.oid = a.attrelid\n JOIN\n pg_catalog.pg_namespace n ON n.oid = cl.relnamespace\n JOIN\n pg_catalog.pg_type t ON t.oid = a.atttypid\n JOIN\n pg_catalog.pg_type ut ON ut.oid = CASE WHEN t.typtype = 'd' THEN t.typbasetype ELSE t.oid END\n JOIN\n pg_catalog.pg_namespace nut ON nut.oid = ut.typnamespace\n LEFT JOIN\n pg_catalog.pg_type et ON et.oid = ut.typelem\n CROSS JOIN LATERAL\n (SELECT CASE WHEN t.typtype = 'd' THEN t.typtypmod ELSE a.atttypmod END AS typmod) tm\n WHERE\n a.attnum > 0\n AND NOT a.attisdropped\n AND cl.relkind IN ('r', 'v', 'f', 'p')\n AND NOT pg_catalog.pg_is_other_temp_schema(n.oid)\n AND (pg_catalog.pg_has_role(cl.relowner, 'USAGE')\n OR pg_catalog.has_column_privilege(cl.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'))\n AND (n.nspname, cl.relname) IN (${inClause})\n `;\n return formatPg(query);\n}\n\nfunction makeSchemaIncompatibleErrorMessage(\n errors: SchemaIncompatibilityError[],\n) {\n if (errors.length === 0) {\n return 'No schema incompatibilities found.';\n }\n\n const messages: string[] = [];\n\n for (const error of errors) {\n switch (error.type) {\n case 'missingTable':\n messages.push(\n `Table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'missingColumn':\n messages.push(\n `Column \"${error.column}\" in table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'typeError':\n messages.push(\n `Type mismatch for column \"${error.column}\" in table \"${error.table}\": ${error.requiredType === undefined ? `${error.pgType} is currently unsupported in Zero. Please file a bug at https://bugs.rocicorp.dev/` : `${error.pgType} should be mapped to ${error.requiredType} in Zero not ${error.declaredType}.`}`,\n );\n break;\n }\n }\n\n return [\n 'Schema incompatibility detected between your zero schema definition and the database:',\n '',\n ...messages.map(msg => ` - ${msg}`),\n '',\n 'Please update your schema definition to match the database or migrate your database to match the schema.',\n ].join('\\n');\n}\n\nexport type SchemaIncompatibilityError =\n | {\n type: 'typeError';\n table: string;\n column: string;\n pgType: string;\n declaredType: string;\n requiredType: string | undefined;\n }\n | {\n type: 'missingColumn';\n table: string;\n column: string;\n }\n | {\n type: 'missingTable';\n table: string;\n };\n\nexport function checkSchemasAreCompatible(\n schema: Schema,\n serverSchema: ServerSchema,\n): SchemaIncompatibilityError[] {\n const errors: SchemaIncompatibilityError[] = [];\n // Check that all tables in schema exist in serverSchema\n for (const table of Object.values(schema.tables)) {\n const serverTableName = table.serverName ?? table.name;\n\n if (!serverSchema[serverTableName]) {\n errors.push({\n type: 'missingTable',\n table: serverTableName,\n });\n continue;\n }\n\n // Check that all columns in the table exist in serverSchema\n for (const [columnName, column] of Object.entries(table.columns)) {\n const serverColumnName = column.serverName ?? columnName;\n\n if (!serverSchema[serverTableName][serverColumnName]) {\n errors.push({\n type: 'missingColumn',\n table: serverTableName,\n column: serverColumnName,\n });\n continue;\n }\n\n // Check type compatibility\n const serverColumn = serverSchema[serverTableName][serverColumnName];\n const declaredType = column.type;\n const pgType = serverColumn.type;\n const requiredType = dataTypeToZqlValueType(\n pgType,\n serverColumn.isEnum,\n serverColumn.isArray,\n );\n if (requiredType !== declaredType) {\n errors.push({\n type: 'typeError',\n table: serverTableName,\n column: serverColumnName,\n pgType,\n declaredType,\n requiredType,\n });\n }\n }\n }\n\n return errors;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../../zero-server/src/schema.ts"],"sourcesContent":["import {assert} from '../../shared/src/asserts.ts';\nimport type {Enum} from '../../shared/src/enum.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport * as PostgresTypeClass from '../../zero-cache/src/db/postgres-type-class-enum.ts';\nimport {dataTypeToZqlValueType} from '../../zero-cache/src/types/pg-data-type.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {ServerSchema} from '../../zero-types/src/server-schema.ts';\nimport type {Queryable} from '../../zql/src/mutate/custom.ts';\n\ntype PostgresTypeClass = Enum<typeof PostgresTypeClass>;\n\ntype ServerSchemaRow = {\n schema: string;\n table: string;\n column: string;\n dataType: string;\n length: string | null;\n precision: string | null;\n scale: string | null;\n typtype: PostgresTypeClass;\n typename: string;\n elemTyptype: PostgresTypeClass | null;\n elemTypname: string | null;\n};\n\nexport async function getServerSchema<S extends Schema>(\n queryable: Queryable,\n schema: S,\n): Promise<ServerSchema> {\n const schemaTablePairs: [string, string][] = Object.values(schema.tables).map(\n ({name, serverName}) => {\n let schemaTablePair: [string, string] = ['public', serverName ?? name];\n if (serverName) {\n const firstPeriod = serverName.indexOf('.');\n if (firstPeriod > -1) {\n schemaTablePair = [\n serverName.substring(0, firstPeriod),\n serverName.substring(firstPeriod + 1, serverName.length),\n ];\n }\n }\n return schemaTablePair;\n },\n );\n\n if (schemaTablePairs.length === 0) {\n return {}; // No pairs to query for\n }\n\n const {text, values} = serverSchemaQuery(schemaTablePairs);\n const results: Iterable<ServerSchemaRow> = (await queryable.query(\n text,\n values,\n )) as Iterable<ServerSchemaRow>;\n\n const serverSchema: ServerSchema = {};\n\n for (const row of results) {\n const tableName =\n row.schema === 'public' ? row.table : `${row.schema}.${row.table}`;\n let tableSchema = serverSchema[tableName];\n if (!tableSchema) {\n tableSchema = {};\n serverSchema[tableName] = tableSchema;\n }\n const isArray = row.elemTyptype !== null;\n const isEnum = (row.elemTyptype ?? row.typtype) === PostgresTypeClass.Enum;\n let type = row.dataType.toLowerCase();\n if (isArray) {\n type = must(\n row.elemTypname,\n `Array column \"${row.column}\" in table \"${tableName}\" is missing element type name`,\n );\n } else if (isEnum) {\n type = row.typename;\n } else if (\n type === 'user-defined' &&\n dataTypeToZqlValueType(row.typename, false, false) !== undefined\n ) {\n type = row.typename;\n }\n if (\n (type === 'bpchar' ||\n type === 'character' ||\n type === 'character varying' ||\n type === 'varchar') &&\n row.length\n ) {\n type = `${type}(${row.length})`;\n } else if (type === 'character') {\n // Without a length `character` means `character(1)`, so, like Postgres's\n // own format_type, name an unbounded `bpchar` column `bpchar`.\n type = 'bpchar';\n }\n if (\n (type === 'numeric' || type === 'decimal') &&\n row.precision !== null &&\n row.scale !== null\n ) {\n type = `${type}(${row.precision}, ${row.scale})`;\n }\n tableSchema[row.column] = {\n type,\n isEnum,\n isArray,\n };\n }\n\n const errors = checkSchemasAreCompatible(schema, serverSchema);\n assert(errors.length === 0, () => makeSchemaIncompatibleErrorMessage(errors));\n\n return serverSchema;\n}\n\n/**\n * The query behind {@link getServerSchema}, exported so that it can be\n * diffed against the `information_schema` query it replaced.\n */\nexport function serverSchemaQuery(\n schemaTablePairs: readonly (readonly [string, string])[],\n): {text: string; values: unknown[]} {\n // Cast all inputs to text and all outputs to text to avoid\n // any conversions customer's DBTransaction impl has on other types.\n const inClause = sql.join(\n schemaTablePairs.map(\n ([schema, table]) => sql`(${schema}::text, ${table}::text)`,\n ),\n ',',\n );\n // Read pg_catalog directly instead of information_schema.columns. The view\n // derives every column it returns through the information_schema._pg_*\n // functions and a per column privilege check, which is several times slower\n // than the query below. Server processes run this on the first transaction\n // they serve, so serverless deployments pay for it on every cold start.\n //\n // The expressions below reproduce the parts of the view's definition that we\n // select, down to the int4 wire types of length, precision and scale: the\n // _pg_char_max_length / _pg_numeric_precision / _pg_numeric_scale arithmetic,\n // the single level domain unwrap that data_type and udt_name perform (`ut`),\n // and the view's own relkind, attnum and privilege filters. pg_type is joined\n // by OID rather than by udt_name, which additionally avoids duplicate rows\n // when two schemas declare a type with the same name. The types those\n // functions special case are named through regtype, which the parser resolves\n // while planning.\n const query = sql`\n SELECT\n n.nspname::text AS schema,\n cl.relname::text AS table,\n a.attname::text AS column,\n CASE\n WHEN ut.typelem <> 0 AND ut.typlen = -1 THEN 'ARRAY'\n WHEN nut.nspname = 'pg_catalog' THEN pg_catalog.format_type(ut.oid, NULL)\n ELSE 'USER-DEFINED'\n END::text AS \"dataType\",\n CASE\n WHEN tm.typmod = -1 THEN NULL\n WHEN ut.oid IN ('pg_catalog.bpchar'::regtype, 'pg_catalog.varchar'::regtype) THEN tm.typmod - 4\n WHEN ut.oid IN ('pg_catalog.bit'::regtype, 'pg_catalog.varbit'::regtype) THEN tm.typmod\n END AS length,\n CASE ut.oid\n WHEN 'pg_catalog.int2'::regtype THEN 16\n WHEN 'pg_catalog.int4'::regtype THEN 32\n WHEN 'pg_catalog.int8'::regtype THEN 64\n WHEN 'pg_catalog.float4'::regtype THEN 24\n WHEN 'pg_catalog.float8'::regtype THEN 53\n WHEN 'pg_catalog.numeric'::regtype THEN CASE WHEN tm.typmod = -1 THEN NULL ELSE ((tm.typmod - 4) >> 16) & 65535 END\n END AS precision,\n CASE\n WHEN ut.oid IN ('pg_catalog.int2'::regtype, 'pg_catalog.int4'::regtype, 'pg_catalog.int8'::regtype) THEN 0\n WHEN ut.oid = 'pg_catalog.numeric'::regtype AND tm.typmod <> -1 THEN (tm.typmod - 4) & 65535\n END AS scale,\n ut.typtype::text AS typtype,\n ut.typname::text AS typename,\n CASE WHEN ut.typelem <> 0 THEN et.typtype::text ELSE NULL END AS \"elemTyptype\",\n CASE WHEN ut.typelem <> 0 THEN et.typname::text ELSE NULL END AS \"elemTypname\"\n FROM\n pg_catalog.pg_attribute a\n JOIN\n pg_catalog.pg_class cl ON cl.oid = a.attrelid\n JOIN\n pg_catalog.pg_namespace n ON n.oid = cl.relnamespace\n JOIN\n pg_catalog.pg_type t ON t.oid = a.atttypid\n JOIN\n pg_catalog.pg_type ut ON ut.oid = CASE WHEN t.typtype = 'd' THEN t.typbasetype ELSE t.oid END\n JOIN\n pg_catalog.pg_namespace nut ON nut.oid = ut.typnamespace\n LEFT JOIN\n pg_catalog.pg_type et ON et.oid = ut.typelem\n CROSS JOIN LATERAL\n (SELECT CASE WHEN t.typtype = 'd' THEN t.typtypmod ELSE a.atttypmod END AS typmod) tm\n WHERE\n a.attnum > 0\n AND NOT a.attisdropped\n AND cl.relkind IN ('r', 'v', 'f', 'p')\n AND NOT pg_catalog.pg_is_other_temp_schema(n.oid)\n AND (pg_catalog.pg_has_role(cl.relowner, 'USAGE')\n OR pg_catalog.has_column_privilege(cl.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'))\n AND (n.nspname, cl.relname) IN (${inClause})\n `;\n return formatPg(query);\n}\n\nfunction makeSchemaIncompatibleErrorMessage(\n errors: SchemaIncompatibilityError[],\n) {\n if (errors.length === 0) {\n return 'No schema incompatibilities found.';\n }\n\n const messages: string[] = [];\n\n for (const error of errors) {\n switch (error.type) {\n case 'missingTable':\n messages.push(\n `Table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'missingColumn':\n messages.push(\n `Column \"${error.column}\" in table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'typeError':\n messages.push(\n `Type mismatch for column \"${error.column}\" in table \"${error.table}\": ${error.requiredType === undefined ? `${error.pgType} is currently unsupported in Zero. Please file a bug at https://bugs.rocicorp.dev/` : `${error.pgType} should be mapped to ${error.requiredType} in Zero not ${error.declaredType}.`}`,\n );\n break;\n }\n }\n\n return [\n 'Schema incompatibility detected between your zero schema definition and the database:',\n '',\n ...messages.map(msg => ` - ${msg}`),\n '',\n 'Please update your schema definition to match the database or migrate your database to match the schema.',\n ].join('\\n');\n}\n\nexport type SchemaIncompatibilityError =\n | {\n type: 'typeError';\n table: string;\n column: string;\n pgType: string;\n declaredType: string;\n requiredType: string | undefined;\n }\n | {\n type: 'missingColumn';\n table: string;\n column: string;\n }\n | {\n type: 'missingTable';\n table: string;\n };\n\nexport function checkSchemasAreCompatible(\n schema: Schema,\n serverSchema: ServerSchema,\n): SchemaIncompatibilityError[] {\n const errors: SchemaIncompatibilityError[] = [];\n // Check that all tables in schema exist in serverSchema\n for (const table of Object.values(schema.tables)) {\n const serverTableName = table.serverName ?? table.name;\n\n if (!serverSchema[serverTableName]) {\n errors.push({\n type: 'missingTable',\n table: serverTableName,\n });\n continue;\n }\n\n // Check that all columns in the table exist in serverSchema\n for (const [columnName, column] of Object.entries(table.columns)) {\n const serverColumnName = column.serverName ?? columnName;\n\n if (!serverSchema[serverTableName][serverColumnName]) {\n errors.push({\n type: 'missingColumn',\n table: serverTableName,\n column: serverColumnName,\n });\n continue;\n }\n\n // Check type compatibility\n const serverColumn = serverSchema[serverTableName][serverColumnName];\n const declaredType = column.type;\n const pgType = serverColumn.type;\n const requiredType = dataTypeToZqlValueType(\n pgType,\n serverColumn.isEnum,\n serverColumn.isArray,\n );\n if (requiredType !== declaredType) {\n errors.push({\n type: 'typeError',\n table: serverTableName,\n column: serverColumnName,\n pgType,\n declaredType,\n requiredType,\n });\n }\n }\n }\n\n return errors;\n}\n"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ export declare class ZQLDatabase<TSchema extends Schema, TWrappedTransaction> im
|
|
|
19
19
|
/**
|
|
20
20
|
* Runs a single read query.
|
|
21
21
|
*
|
|
22
|
-
* When the {@
|
|
22
|
+
* When the {@link DBConnection} implements `query`, the compiled SQL is
|
|
23
23
|
* issued as a bare statement with no `BEGIN`/`COMMIT` around it (plus a
|
|
24
24
|
* one-time server schema lookup the first time this instance touches the
|
|
25
25
|
* database). Postgres then releases the statement's locks the moment it
|
|
@@ -34,7 +34,7 @@ export declare class ZQLDatabase<TSchema extends Schema, TWrappedTransaction> im
|
|
|
34
34
|
* transaction as before.
|
|
35
35
|
*
|
|
36
36
|
* If you need multiple reads to observe a consistent snapshot, use
|
|
37
|
-
* {@
|
|
37
|
+
* {@link transaction} and call `tx.run(...)` for each query instead.
|
|
38
38
|
*/
|
|
39
39
|
run<TTable extends keyof TSchema['tables'] & string, TReturn>(query: Query<TTable, TSchema, TReturn>, _options?: RunOptions): Promise<HumanReadable<TReturn>>;
|
|
40
40
|
}
|
|
@@ -68,7 +68,7 @@ var ZQLDatabase = class {
|
|
|
68
68
|
/**
|
|
69
69
|
* Runs a single read query.
|
|
70
70
|
*
|
|
71
|
-
* When the {@
|
|
71
|
+
* When the {@link DBConnection} implements `query`, the compiled SQL is
|
|
72
72
|
* issued as a bare statement with no `BEGIN`/`COMMIT` around it (plus a
|
|
73
73
|
* one-time server schema lookup the first time this instance touches the
|
|
74
74
|
* database). Postgres then releases the statement's locks the moment it
|
|
@@ -83,7 +83,7 @@ var ZQLDatabase = class {
|
|
|
83
83
|
* transaction as before.
|
|
84
84
|
*
|
|
85
85
|
* If you need multiple reads to observe a consistent snapshot, use
|
|
86
|
-
* {@
|
|
86
|
+
* {@link transaction} and call `tx.run(...)` for each query instead.
|
|
87
87
|
*/
|
|
88
88
|
run(query, _options) {
|
|
89
89
|
const { connection } = this;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zql-database.js","names":["#crudFactory","#schema","#makeServerTransaction","#runOn"],"sources":["../../../../zero-server/src/zql-database.ts"],"sourcesContent":["import type {MaybePromise} from '../../shared/src/types.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport type {CleanupResultsArg} from '../../zero-protocol/src/mutation.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {\n DBConnection,\n DBTransaction,\n Queryable,\n RetryPredicate,\n} from '../../zql/src/mutate/custom.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport type {\n HumanReadable,\n Query,\n RunOptions,\n} from '../../zql/src/query/query.ts';\nimport {ClosableDBTransaction} from './closable-db-transaction.ts';\nimport {CRUDMutatorFactory, type TransactionImpl} from './custom.ts';\nimport {executePostgresQuery} from './pg-query-executor.ts';\nimport type {\n Database,\n TransactionProviderHooks,\n TransactionProviderInput,\n} from './process-mutations.ts';\n\n/**\n * Implements a Database for use with PushProcessor that is backed by Postgres.\n *\n * This implementation also implements the same ZQL interfaces for reading and\n * writing data that the Zero client does, so that mutator functions can be\n * shared across client and server.\n */\nexport class ZQLDatabase<\n TSchema extends Schema,\n TWrappedTransaction,\n> implements Database<TransactionImpl<TSchema, TWrappedTransaction>> {\n readonly connection: DBConnection<TWrappedTransaction>;\n readonly #crudFactory: CRUDMutatorFactory<TSchema>;\n readonly #schema: TSchema;\n\n constructor(connection: DBConnection<TWrappedTransaction>, schema: TSchema) {\n this.connection = connection;\n this.#crudFactory = new CRUDMutatorFactory(schema);\n this.#schema = schema;\n }\n\n transaction<R>(\n callback: (\n tx: TransactionImpl<TSchema, TWrappedTransaction>,\n transactionHooks: TransactionProviderHooks,\n ) => MaybePromise<R>,\n transactionInput?: TransactionProviderInput,\n ): Promise<R> {\n // Icky hack. This is just here to have user not have to do this.\n // These interfaces need to be factored better.\n const {\n upstreamSchema = '',\n clientGroupID = '',\n clientID = '',\n mutationID = 0,\n addRetryPredicate,\n } = transactionInput ?? {};\n return this.connection.transaction(async dbTx => {\n // What the mutator's `tx` uses. Closed once the callback settles, so a\n // `tx` that outlives its transaction, such as one captured by a\n // `tx.retryOn()` predicate, throws instead of reaching a connection\n // that may already be back in the pool.\n const mutatorTx = new ClosableDBTransaction(dbTx);\n try {\n const zeroTx = await this.#makeServerTransaction(\n mutatorTx,\n clientID,\n mutationID,\n addRetryPredicate,\n );\n\n return await callback(zeroTx, {\n async updateClientMutationID() {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.clients \n as current (\"clientGroupID\", \"clientID\", \"lastMutationID\")\n VALUES (${clientGroupID}, ${clientID}, ${1})\n ON CONFLICT (\"clientGroupID\", \"clientID\")\n DO UPDATE SET \"lastMutationID\" = current.\"lastMutationID\" + 1\n RETURNING \"lastMutationID\"`,\n );\n\n const [{lastMutationID}] = (await dbTx.query(\n formatted.text,\n formatted.values,\n )) as {lastMutationID: bigint}[];\n\n return {lastMutationID};\n },\n\n async writeMutationResult(result) {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.mutations\n (\"clientGroupID\", \"clientID\", \"mutationID\", \"result\")\n VALUES (${clientGroupID}, ${result.id.clientID}, ${result.id.id}, ${JSON.stringify(\n result.result,\n )}::text::json)`,\n );\n await dbTx.query(formatted.text, formatted.values);\n },\n\n async deleteMutationResults(args: CleanupResultsArg) {\n if ('type' in args && args.type === 'bulk') {\n // Bulk deletion: delete all mutations for multiple clients\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ANY(${args.clientIDs})`,\n );\n await dbTx.query(formatted.text, formatted.values);\n } else {\n // Single client (explicit 'single' or legacy without type): delete up to mutation ID\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ${args.clientID}\n AND \"mutationID\" <= ${args.upToMutationID}`,\n );\n await dbTx.query(formatted.text, formatted.values);\n }\n },\n });\n } finally {\n mutatorTx.close();\n }\n });\n }\n\n #makeServerTransaction(\n dbTx: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n addRetryPredicate: ((shouldRetry: RetryPredicate) => void) | undefined,\n ) {\n return this.#crudFactory.createTransaction(\n dbTx,\n clientID,\n mutationID,\n addRetryPredicate,\n );\n }\n\n /**\n * Runs a single read query.\n *\n * When the {@
|
|
1
|
+
{"version":3,"file":"zql-database.js","names":["#crudFactory","#schema","#makeServerTransaction","#runOn"],"sources":["../../../../zero-server/src/zql-database.ts"],"sourcesContent":["import type {MaybePromise} from '../../shared/src/types.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport type {CleanupResultsArg} from '../../zero-protocol/src/mutation.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {\n DBConnection,\n DBTransaction,\n Queryable,\n RetryPredicate,\n} from '../../zql/src/mutate/custom.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport type {\n HumanReadable,\n Query,\n RunOptions,\n} from '../../zql/src/query/query.ts';\nimport {ClosableDBTransaction} from './closable-db-transaction.ts';\nimport {CRUDMutatorFactory, type TransactionImpl} from './custom.ts';\nimport {executePostgresQuery} from './pg-query-executor.ts';\nimport type {\n Database,\n TransactionProviderHooks,\n TransactionProviderInput,\n} from './process-mutations.ts';\n\n/**\n * Implements a Database for use with PushProcessor that is backed by Postgres.\n *\n * This implementation also implements the same ZQL interfaces for reading and\n * writing data that the Zero client does, so that mutator functions can be\n * shared across client and server.\n */\nexport class ZQLDatabase<\n TSchema extends Schema,\n TWrappedTransaction,\n> implements Database<TransactionImpl<TSchema, TWrappedTransaction>> {\n readonly connection: DBConnection<TWrappedTransaction>;\n readonly #crudFactory: CRUDMutatorFactory<TSchema>;\n readonly #schema: TSchema;\n\n constructor(connection: DBConnection<TWrappedTransaction>, schema: TSchema) {\n this.connection = connection;\n this.#crudFactory = new CRUDMutatorFactory(schema);\n this.#schema = schema;\n }\n\n transaction<R>(\n callback: (\n tx: TransactionImpl<TSchema, TWrappedTransaction>,\n transactionHooks: TransactionProviderHooks,\n ) => MaybePromise<R>,\n transactionInput?: TransactionProviderInput,\n ): Promise<R> {\n // Icky hack. This is just here to have user not have to do this.\n // These interfaces need to be factored better.\n const {\n upstreamSchema = '',\n clientGroupID = '',\n clientID = '',\n mutationID = 0,\n addRetryPredicate,\n } = transactionInput ?? {};\n return this.connection.transaction(async dbTx => {\n // What the mutator's `tx` uses. Closed once the callback settles, so a\n // `tx` that outlives its transaction, such as one captured by a\n // `tx.retryOn()` predicate, throws instead of reaching a connection\n // that may already be back in the pool.\n const mutatorTx = new ClosableDBTransaction(dbTx);\n try {\n const zeroTx = await this.#makeServerTransaction(\n mutatorTx,\n clientID,\n mutationID,\n addRetryPredicate,\n );\n\n return await callback(zeroTx, {\n async updateClientMutationID() {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.clients \n as current (\"clientGroupID\", \"clientID\", \"lastMutationID\")\n VALUES (${clientGroupID}, ${clientID}, ${1})\n ON CONFLICT (\"clientGroupID\", \"clientID\")\n DO UPDATE SET \"lastMutationID\" = current.\"lastMutationID\" + 1\n RETURNING \"lastMutationID\"`,\n );\n\n const [{lastMutationID}] = (await dbTx.query(\n formatted.text,\n formatted.values,\n )) as {lastMutationID: bigint}[];\n\n return {lastMutationID};\n },\n\n async writeMutationResult(result) {\n const formatted = formatPg(\n sql`INSERT INTO ${sql.ident(upstreamSchema)}.mutations\n (\"clientGroupID\", \"clientID\", \"mutationID\", \"result\")\n VALUES (${clientGroupID}, ${result.id.clientID}, ${result.id.id}, ${JSON.stringify(\n result.result,\n )}::text::json)`,\n );\n await dbTx.query(formatted.text, formatted.values);\n },\n\n async deleteMutationResults(args: CleanupResultsArg) {\n if ('type' in args && args.type === 'bulk') {\n // Bulk deletion: delete all mutations for multiple clients\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ANY(${args.clientIDs})`,\n );\n await dbTx.query(formatted.text, formatted.values);\n } else {\n // Single client (explicit 'single' or legacy without type): delete up to mutation ID\n const formatted = formatPg(\n sql`DELETE FROM ${sql.ident(upstreamSchema)}.\"mutations\"\n WHERE \"clientGroupID\" = ${args.clientGroupID}\n AND \"clientID\" = ${args.clientID}\n AND \"mutationID\" <= ${args.upToMutationID}`,\n );\n await dbTx.query(formatted.text, formatted.values);\n }\n },\n });\n } finally {\n mutatorTx.close();\n }\n });\n }\n\n #makeServerTransaction(\n dbTx: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n addRetryPredicate: ((shouldRetry: RetryPredicate) => void) | undefined,\n ) {\n return this.#crudFactory.createTransaction(\n dbTx,\n clientID,\n mutationID,\n addRetryPredicate,\n );\n }\n\n /**\n * Runs a single read query.\n *\n * When the {@link DBConnection} implements `query`, the compiled SQL is\n * issued as a bare statement with no `BEGIN`/`COMMIT` around it (plus a\n * one-time server schema lookup the first time this instance touches the\n * database). Postgres then releases the statement's locks the moment it\n * finishes instead of waiting for a `COMMIT` round-trip, which matters in\n * serverless environments where the process can be frozen or reclaimed\n * between the query resolving and the `COMMIT` being sent, leaving the\n * transaction and its locks open indefinitely.\n *\n * Because no transaction is opened, any per-transaction setup the adapter\n * performs inside `transaction` does not apply to these reads. If the\n * connection does not implement `query`, the read is wrapped in a\n * transaction as before.\n *\n * If you need multiple reads to observe a consistent snapshot, use\n * {@link transaction} and call `tx.run(...)` for each query instead.\n */\n run<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TTable, TSchema, TReturn>,\n _options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n const {connection} = this;\n if (connection.query) {\n // TS narrows `connection.query`, not `connection` itself.\n return this.#runOn(connection as ReadTarget<TWrappedTransaction>, query);\n }\n return connection.transaction(dbTx => this.#runOn(dbTx, query));\n }\n\n async #runOn<TTable extends keyof TSchema['tables'] & string, TReturn>(\n target: ReadTarget<TWrappedTransaction>,\n query: Query<TTable, TSchema, TReturn>,\n ): Promise<HumanReadable<TReturn>> {\n const {ast, format} = asQueryInternals(query);\n const serverSchema = await this.#crudFactory.getOrFetchServerSchema(target);\n return target.runQuery\n ? target.runQuery<TReturn>(ast, format, this.#schema, serverSchema)\n : executePostgresQuery<TReturn>(\n target,\n ast,\n format,\n this.#schema,\n serverSchema,\n );\n }\n}\n\n/**\n * Something a read can be executed against: a `DBTransaction`, or a\n * `DBConnection` that implements `query`.\n */\ntype ReadTarget<TWrappedTransaction> = Queryable & {\n runQuery?: DBTransaction<TWrappedTransaction>['runQuery'] | undefined;\n};\n"],"mappings":";;;;;;;;;;;;;AAgCA,IAAa,cAAb,MAGqE;CACnE;CACA;CACA;CAEA,YAAY,YAA+C,QAAiB;EAC1E,KAAK,aAAa;EAClB,KAAKA,eAAe,IAAI,mBAAmB,MAAM;EACjD,KAAKC,UAAU;CACjB;CAEA,YACE,UAIA,kBACY;EAGZ,MAAM,EACJ,iBAAiB,IACjB,gBAAgB,IAChB,WAAW,IACX,aAAa,GACb,sBACE,oBAAoB,CAAC;EACzB,OAAO,KAAK,WAAW,YAAY,OAAM,SAAQ;GAK/C,MAAM,YAAY,IAAI,sBAAsB,IAAI;GAChD,IAAI;IAQF,OAAO,MAAM,SAAS,MAPD,KAAKC,uBACxB,WACA,UACA,YACA,iBACF,GAE8B;KAC5B,MAAM,yBAAyB;MAC7B,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,cAAc,EAAE;;kCAExB,cAAc,IAAI,SAAS,IAAI,EAAE;;;+CAIvD;MAEA,MAAM,CAAC,EAAC,oBAAoB,MAAM,KAAK,MACrC,UAAU,MACV,UAAU,MACZ;MAEA,OAAO,EAAC,eAAc;KACxB;KAEA,MAAM,oBAAoB,QAAQ;MAChC,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,cAAc,EAAE;;0BAEhC,cAAc,IAAI,OAAO,GAAG,SAAS,IAAI,OAAO,GAAG,GAAG,IAAI,KAAK,UACvE,OAAO,MACT,EAAE,cACN;MACA,MAAM,KAAK,MAAM,UAAU,MAAM,UAAU,MAAM;KACnD;KAEA,MAAM,sBAAsB,MAAyB;MACnD,IAAI,UAAU,QAAQ,KAAK,SAAS,QAAQ;OAE1C,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,cAAc,EAAE;4CAChB,KAAK,cAAc;2CACpB,KAAK,UAAU,EAC5C;OACA,MAAM,KAAK,MAAM,UAAU,MAAM,UAAU,MAAM;MACnD,OAAO;OAEL,MAAM,YAAY,SAChB,GAAG,eAAe,IAAI,MAAM,cAAc,EAAE;4CAChB,KAAK,cAAc;uCACxB,KAAK,SAAS;0CACX,KAAK,gBACjC;OACA,MAAM,KAAK,MAAM,UAAU,MAAM,UAAU,MAAM;MACnD;KACF;IACF,CAAC;GACH,UAAU;IACR,UAAU,MAAM;GAClB;EACF,CAAC;CACH;CAEA,uBACE,MACA,UACA,YACA,mBACA;EACA,OAAO,KAAKF,aAAa,kBACvB,MACA,UACA,YACA,iBACF;CACF;;;;;;;;;;;;;;;;;;;;;CAsBA,IACE,OACA,UACiC;EACjC,MAAM,EAAC,eAAc;EACrB,IAAI,WAAW,OAEb,OAAO,KAAKG,OAAO,YAA+C,KAAK;EAEzE,OAAO,WAAW,aAAY,SAAQ,KAAKA,OAAO,MAAM,KAAK,CAAC;CAChE;CAEA,MAAMA,OACJ,QACA,OACiC;EACjC,MAAM,EAAC,KAAK,WAAU,iBAAiB,KAAK;EAC5C,MAAM,eAAe,MAAM,KAAKH,aAAa,uBAAuB,MAAM;EAC1E,OAAO,OAAO,WACV,OAAO,SAAkB,KAAK,QAAQ,KAAKC,SAAS,YAAY,IAChE,qBACE,QACA,KACA,QACA,KAAKA,SACL,YACF;CACN;AACF"}
|
|
@@ -13,7 +13,7 @@ export type MaybeQueryResult<TReturn> = readonly [
|
|
|
13
13
|
Accessor<QueryResultDetails & {}>
|
|
14
14
|
];
|
|
15
15
|
/**
|
|
16
|
-
* @deprecated Use {@
|
|
16
|
+
* @deprecated Use {@link UseQueryOptions} instead.
|
|
17
17
|
*/
|
|
18
18
|
export type CreateQueryOptions = {
|
|
19
19
|
ttl?: TTL | undefined;
|
|
@@ -22,7 +22,7 @@ export type UseQueryOptions = {
|
|
|
22
22
|
ttl?: TTL | undefined;
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
* @deprecated Use {@
|
|
25
|
+
* @deprecated Use {@link useQuery} instead.
|
|
26
26
|
*/
|
|
27
27
|
export declare function createQuery<TTable extends keyof TSchema['tables'] & string, TInput extends ReadonlyJSONValue | undefined, TOutput extends ReadonlyJSONValue | undefined, TSchema extends BaseDefaultSchema = DefaultSchema, TReturn = PullRow<TTable, TSchema>, TContext extends BaseDefaultContext = DefaultContext>(querySignal: Accessor<QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>>, options?: CreateQueryOptions | Accessor<CreateQueryOptions>): QueryResult<TReturn>;
|
|
28
28
|
export declare function useQuery<TTable extends keyof TSchema['tables'] & string, TInput extends ReadonlyJSONValue | undefined, TOutput extends ReadonlyJSONValue | undefined, TSchema extends BaseDefaultSchema = DefaultSchema, TReturn = PullRow<TTable, TSchema>, TContext extends BaseDefaultContext = DefaultContext>(querySignal: Accessor<QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>>, options?: UseQueryOptions | Accessor<UseQueryOptions>): QueryResult<TReturn>;
|
|
@@ -9,7 +9,7 @@ import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "
|
|
|
9
9
|
import { createStore } from "solid-js/store";
|
|
10
10
|
//#region ../zero-solid/src/use-query.ts
|
|
11
11
|
/**
|
|
12
|
-
* @deprecated Use {@
|
|
12
|
+
* @deprecated Use {@link useQuery} instead.
|
|
13
13
|
*/
|
|
14
14
|
function createQuery(querySignal, options) {
|
|
15
15
|
return useQuery(querySignal, options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-query.js","names":[],"sources":["../../../../zero-solid/src/use-query.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n on,\n onCleanup,\n untrack,\n type Accessor,\n} from 'solid-js';\nimport {createStore} from 'solid-js/store';\nimport {\n addContextToQuery,\n asQueryInternals,\n DEFAULT_TTL_MS,\n} from './bindings.ts';\nimport {createSolidViewFactory, UNKNOWN, type State} from './solid-view.ts';\nimport {useZero} from './use-zero.ts';\nimport {\n type BaseDefaultContext,\n type BaseDefaultSchema,\n type DefaultContext,\n type DefaultSchema,\n type Falsy,\n type HumanReadable,\n type PullRow,\n type QueryOrQueryRequest,\n type QueryResultDetails,\n type ReadonlyJSONValue,\n type TTL,\n} from './zero.ts';\n\nexport type QueryResult<TReturn> = readonly [\n Accessor<HumanReadable<TReturn>>,\n Accessor<QueryResultDetails & {}>,\n];\n\n/**\n * Result type for \"maybe queries\" - queries that may be falsy.\n * The data value can be undefined when the query is falsy/disabled.\n */\nexport type MaybeQueryResult<TReturn> = readonly [\n Accessor<HumanReadable<TReturn> | undefined>,\n Accessor<QueryResultDetails & {}>,\n];\n\n// Deprecated in 0.22\n/**\n * @deprecated Use {@
|
|
1
|
+
{"version":3,"file":"use-query.js","names":[],"sources":["../../../../zero-solid/src/use-query.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n on,\n onCleanup,\n untrack,\n type Accessor,\n} from 'solid-js';\nimport {createStore} from 'solid-js/store';\nimport {\n addContextToQuery,\n asQueryInternals,\n DEFAULT_TTL_MS,\n} from './bindings.ts';\nimport {createSolidViewFactory, UNKNOWN, type State} from './solid-view.ts';\nimport {useZero} from './use-zero.ts';\nimport {\n type BaseDefaultContext,\n type BaseDefaultSchema,\n type DefaultContext,\n type DefaultSchema,\n type Falsy,\n type HumanReadable,\n type PullRow,\n type QueryOrQueryRequest,\n type QueryResultDetails,\n type ReadonlyJSONValue,\n type TTL,\n} from './zero.ts';\n\nexport type QueryResult<TReturn> = readonly [\n Accessor<HumanReadable<TReturn>>,\n Accessor<QueryResultDetails & {}>,\n];\n\n/**\n * Result type for \"maybe queries\" - queries that may be falsy.\n * The data value can be undefined when the query is falsy/disabled.\n */\nexport type MaybeQueryResult<TReturn> = readonly [\n Accessor<HumanReadable<TReturn> | undefined>,\n Accessor<QueryResultDetails & {}>,\n];\n\n// Deprecated in 0.22\n/**\n * @deprecated Use {@link UseQueryOptions} instead.\n */\nexport type CreateQueryOptions = {\n ttl?: TTL | undefined;\n};\n\nexport type UseQueryOptions = {\n ttl?: TTL | undefined;\n};\n\n// Deprecated in 0.22\n/**\n * @deprecated Use {@link useQuery} instead.\n */\nexport function createQuery<\n TTable extends keyof TSchema['tables'] & string,\n TInput extends ReadonlyJSONValue | undefined,\n TOutput extends ReadonlyJSONValue | undefined,\n TSchema extends BaseDefaultSchema = DefaultSchema,\n TReturn = PullRow<TTable, TSchema>,\n TContext extends BaseDefaultContext = DefaultContext,\n>(\n querySignal: Accessor<\n QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>\n >,\n options?: CreateQueryOptions | Accessor<CreateQueryOptions>,\n): QueryResult<TReturn> {\n return useQuery(querySignal, options);\n}\n\n// Overload 1: Query - returns QueryResult<TReturn>\nexport function useQuery<\n TTable extends keyof TSchema['tables'] & string,\n TInput extends ReadonlyJSONValue | undefined,\n TOutput extends ReadonlyJSONValue | undefined,\n TSchema extends BaseDefaultSchema = DefaultSchema,\n TReturn = PullRow<TTable, TSchema>,\n TContext extends BaseDefaultContext = DefaultContext,\n>(\n querySignal: Accessor<\n QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>\n >,\n options?: UseQueryOptions | Accessor<UseQueryOptions>,\n): QueryResult<TReturn>;\n\n// Overload 2: Maybe query\nexport function useQuery<\n TTable extends keyof TSchema['tables'] & string,\n TInput extends ReadonlyJSONValue | undefined,\n TOutput extends ReadonlyJSONValue | undefined,\n TSchema extends BaseDefaultSchema = DefaultSchema,\n TReturn = PullRow<TTable, TSchema>,\n TContext extends BaseDefaultContext = DefaultContext,\n>(\n querySignal: Accessor<\n | QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>\n | Falsy\n >,\n options?: UseQueryOptions | Accessor<UseQueryOptions>,\n): MaybeQueryResult<TReturn>;\n\n// Implementation\nexport function useQuery<\n TTable extends keyof TSchema['tables'] & string,\n TInput extends ReadonlyJSONValue | undefined,\n TOutput extends ReadonlyJSONValue | undefined,\n TSchema extends BaseDefaultSchema = DefaultSchema,\n TReturn = PullRow<TTable, TSchema>,\n TContext extends BaseDefaultContext = DefaultContext,\n>(\n querySignal: Accessor<\n | QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>\n | Falsy\n >,\n options?: UseQueryOptions | Accessor<UseQueryOptions>,\n): QueryResult<TReturn> | MaybeQueryResult<TReturn> {\n const [state, setState] = createStore<State>([\n {\n '': undefined,\n },\n UNKNOWN,\n ]);\n const initialRefetchKey = 0;\n const [refetchKey, setRefetchKey] = createSignal(initialRefetchKey);\n\n const refetch = () => {\n setRefetchKey(k => k + 1);\n };\n\n const zero = useZero<TSchema, undefined, TContext>();\n\n // Handle possibly falsy queries\n const q = createMemo(() => {\n const query = querySignal();\n if (!query) return undefined;\n return addContextToQuery(query, zero().context);\n });\n\n const qi = createMemo(() => {\n const query = q();\n if (!query) return undefined;\n return asQueryInternals(query);\n });\n\n // Undefined rather than the string `undefined` + the client id: this feeds\n // the guard below, and concatenating a missing hash produced a string that\n // was never equal to undefined, leaving that guard unreachable.\n const hash = createMemo(() => {\n const internals = qi();\n return internals === undefined\n ? undefined\n : internals.hash() + zero().clientID;\n });\n const ttl = createMemo(() => normalize(options)?.ttl ?? DEFAULT_TTL_MS);\n\n const view = createMemo(() => {\n // Depend on hash instead of query to avoid recreating the view when the\n // query object changes but the hash is the same.\n const currentHash = hash();\n refetchKey();\n\n // If query is falsy, don't create a view and reset state to undefined\n if (currentHash === undefined) {\n setState([{'': undefined}, UNKNOWN]);\n return undefined;\n }\n\n const untrackedQuery = untrack(q);\n if (!untrackedQuery) {\n setState([{'': undefined}, UNKNOWN]);\n return undefined;\n }\n\n const v = zero().materialize(\n untrackedQuery,\n createSolidViewFactory(setState, refetch),\n {\n // Untracked: a ttl change updates the live view through the effect\n // below, and depending on it here would tear the view down and build\n // a new one instead. Read at materialize time rather than once at\n // setup, so a view built later -- because the query changed -- gets\n // the ttl in effect by then rather than the one this hook started\n // with.\n ttl: untrack(ttl),\n },\n );\n\n onCleanup(() => v.destroy());\n\n return v;\n });\n\n // Update TTL on existing view when it changes.\n createEffect(\n on(\n ttl,\n currentTTL => {\n view()?.updateTTL(currentTTL);\n },\n {defer: true},\n ),\n );\n\n return [() => state[0][''] as HumanReadable<TReturn>, () => state[1]];\n}\n\nfunction normalize<T>(options?: T | Accessor<T | undefined>): T | undefined {\n return typeof options === 'function' ? (options as Accessor<T>)() : options;\n}\n"],"mappings":";;;;;;;;;;;;;AA6DA,SAAgB,YAQd,aAGA,SACsB;CACtB,OAAO,SAAS,aAAa,OAAO;AACtC;AAkCA,SAAgB,SAQd,aAIA,SACkD;CAClD,MAAM,CAAC,OAAO,YAAY,YAAmB,CAC3C,EACE,IAAI,KAAA,EACN,GACA,OACF,CAAC;CAED,MAAM,CAAC,YAAY,iBAAiB,aAAa,CAAiB;CAElE,MAAM,gBAAgB;EACpB,eAAc,MAAK,IAAI,CAAC;CAC1B;CAEA,MAAM,OAAO,QAAsC;CAGnD,MAAM,IAAI,iBAAiB;EACzB,MAAM,QAAQ,YAAY;EAC1B,IAAI,CAAC,OAAO,OAAO,KAAA;EACnB,OAAO,kBAAkB,OAAO,KAAK,EAAE,OAAO;CAChD,CAAC;CAED,MAAM,KAAK,iBAAiB;EAC1B,MAAM,QAAQ,EAAE;EAChB,IAAI,CAAC,OAAO,OAAO,KAAA;EACnB,OAAO,iBAAiB,KAAK;CAC/B,CAAC;CAKD,MAAM,OAAO,iBAAiB;EAC5B,MAAM,YAAY,GAAG;EACrB,OAAO,cAAc,KAAA,IACjB,KAAA,IACA,UAAU,KAAK,IAAI,KAAK,EAAE;CAChC,CAAC;CACD,MAAM,MAAM,iBAAiB,UAAU,OAAO,GAAG,OAAA,GAAqB;CAEtE,MAAM,OAAO,iBAAiB;EAG5B,MAAM,cAAc,KAAK;EACzB,WAAW;EAGX,IAAI,gBAAgB,KAAA,GAAW;GAC7B,SAAS,CAAC,EAAC,IAAI,KAAA,EAAS,GAAG,OAAO,CAAC;GACnC;EACF;EAEA,MAAM,iBAAiB,QAAQ,CAAC;EAChC,IAAI,CAAC,gBAAgB;GACnB,SAAS,CAAC,EAAC,IAAI,KAAA,EAAS,GAAG,OAAO,CAAC;GACnC;EACF;EAEA,MAAM,IAAI,KAAK,EAAE,YACf,gBACA,uBAAuB,UAAU,OAAO,GACxC,EAOE,KAAK,QAAQ,GAAG,EAClB,CACF;EAEA,gBAAgB,EAAE,QAAQ,CAAC;EAE3B,OAAO;CACT,CAAC;CAGD,aACE,GACE,MACA,eAAc;EACZ,KAAK,GAAG,UAAU,UAAU;CAC9B,GACA,EAAC,OAAO,KAAI,CACd,CACF;CAEA,OAAO,OAAO,MAAM,GAAG,WAAqC,MAAM,EAAE;AACtE;AAEA,SAAS,UAAa,SAAsD;CAC1E,OAAO,OAAO,YAAY,aAAc,QAAwB,IAAI;AACtE"}
|
|
@@ -3,7 +3,7 @@ import { type Accessor } from 'solid-js';
|
|
|
3
3
|
* Tracks the online status of the current Zero instance.
|
|
4
4
|
*
|
|
5
5
|
* @returns An accessor — call `online()` to get a reactive `boolean`.
|
|
6
|
-
* @deprecated Use {@
|
|
6
|
+
* @deprecated Use {@link useConnectionState} instead, which provides more detailed connection state.
|
|
7
7
|
*/
|
|
8
8
|
export declare function useZeroOnline(): Accessor<boolean>;
|
|
9
9
|
//# sourceMappingURL=use-zero-online.d.ts.map
|
|
@@ -5,7 +5,7 @@ import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
|
5
5
|
* Tracks the online status of the current Zero instance.
|
|
6
6
|
*
|
|
7
7
|
* @returns An accessor — call `online()` to get a reactive `boolean`.
|
|
8
|
-
* @deprecated Use {@
|
|
8
|
+
* @deprecated Use {@link useConnectionState} instead, which provides more detailed connection state.
|
|
9
9
|
*/
|
|
10
10
|
function useZeroOnline() {
|
|
11
11
|
const zero = useZero();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-zero-online.js","names":[],"sources":["../../../../zero-solid/src/use-zero-online.ts"],"sourcesContent":["import {createEffect, createSignal, onCleanup, type Accessor} from 'solid-js';\nimport {useZero} from './use-zero.ts';\n\n/**\n * Tracks the online status of the current Zero instance.\n *\n * @returns An accessor — call `online()` to get a reactive `boolean`.\n * @deprecated Use {@
|
|
1
|
+
{"version":3,"file":"use-zero-online.js","names":[],"sources":["../../../../zero-solid/src/use-zero-online.ts"],"sourcesContent":["import {createEffect, createSignal, onCleanup, type Accessor} from 'solid-js';\nimport {useZero} from './use-zero.ts';\n\n/**\n * Tracks the online status of the current Zero instance.\n *\n * @returns An accessor — call `online()` to get a reactive `boolean`.\n * @deprecated Use {@link useConnectionState} instead, which provides more detailed connection state.\n */\nexport function useZeroOnline(): Accessor<boolean> {\n const zero = useZero();\n\n const [online, setOnline] = createSignal<boolean>(zero().online);\n\n createEffect(() => {\n const unsubscribe = zero().onOnline(setOnline);\n\n onCleanup(unsubscribe);\n });\n\n return online;\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,gBAAmC;CACjD,MAAM,OAAO,QAAQ;CAErB,MAAM,CAAC,QAAQ,aAAa,aAAsB,KAAK,EAAE,MAAM;CAE/D,mBAAmB;EAGjB,UAFoB,KAAK,EAAE,SAAS,SAE1B,CAAW;CACvB,CAAC;CAED,OAAO;AACT"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type JSX } from 'solid-js';
|
|
2
2
|
import { Zero, type BaseDefaultContext, type BaseDefaultSchema, type CustomMutatorDefs, type DefaultContext, type DefaultSchema, type ZeroOptions } from './zero.ts';
|
|
3
3
|
/**
|
|
4
|
-
* @deprecated Use {@
|
|
4
|
+
* @deprecated Use {@link ZeroProvider} instead of managing your own Zero instance.
|
|
5
5
|
*/
|
|
6
6
|
export declare function createZero<S extends BaseDefaultSchema = DefaultSchema, MD extends CustomMutatorDefs | undefined = undefined, Context extends BaseDefaultContext = DefaultContext>(options: ZeroOptions<S, MD, Context>): Zero<S, MD, Context>;
|
|
7
7
|
export declare function useZero<S extends BaseDefaultSchema = DefaultSchema, MD extends CustomMutatorDefs | undefined = undefined, Context extends BaseDefaultContext = DefaultContext>(): () => Zero<S, MD, Context>;
|
|
8
8
|
/**
|
|
9
|
-
* @deprecated Use {@
|
|
9
|
+
* @deprecated Use {@link useZero} instead, alongside default types defined with:
|
|
10
10
|
*
|
|
11
11
|
* ```ts
|
|
12
12
|
* declare module '@rocicorp/zero' {
|
|
@@ -4,7 +4,7 @@ import { batch, createContext, createEffect, createMemo, createSignal, onCleanup
|
|
|
4
4
|
//#region ../zero-solid/src/use-zero.ts
|
|
5
5
|
var ZeroContext = createContext(void 0);
|
|
6
6
|
/**
|
|
7
|
-
* @deprecated Use {@
|
|
7
|
+
* @deprecated Use {@link ZeroProvider} instead of managing your own Zero instance.
|
|
8
8
|
*/
|
|
9
9
|
function createZero(options) {
|
|
10
10
|
return new Zero({
|
|
@@ -18,7 +18,7 @@ function useZero() {
|
|
|
18
18
|
return zero;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* @deprecated Use {@
|
|
21
|
+
* @deprecated Use {@link useZero} instead, alongside default types defined with:
|
|
22
22
|
*
|
|
23
23
|
* ```ts
|
|
24
24
|
* declare module '@rocicorp/zero' {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-zero.js","names":[],"sources":["../../../../zero-solid/src/use-zero.ts"],"sourcesContent":["import {\n batch,\n createContext,\n createEffect,\n createMemo,\n createSignal,\n onCleanup,\n splitProps,\n untrack,\n useContext,\n type Accessor,\n type JSX,\n} from 'solid-js';\nimport {\n Zero,\n type BaseDefaultContext,\n type BaseDefaultSchema,\n type CustomMutatorDefs,\n type DefaultContext,\n type DefaultSchema,\n type ZeroOptions,\n} from './zero.ts';\n\nconst ZeroContext = createContext<\n // oxlint-disable-next-line no-explicit-any\n Accessor<Zero<any, any, any>> | undefined\n>(undefined);\n\n/**\n * @deprecated Use {@
|
|
1
|
+
{"version":3,"file":"use-zero.js","names":[],"sources":["../../../../zero-solid/src/use-zero.ts"],"sourcesContent":["import {\n batch,\n createContext,\n createEffect,\n createMemo,\n createSignal,\n onCleanup,\n splitProps,\n untrack,\n useContext,\n type Accessor,\n type JSX,\n} from 'solid-js';\nimport {\n Zero,\n type BaseDefaultContext,\n type BaseDefaultSchema,\n type CustomMutatorDefs,\n type DefaultContext,\n type DefaultSchema,\n type ZeroOptions,\n} from './zero.ts';\n\nconst ZeroContext = createContext<\n // oxlint-disable-next-line no-explicit-any\n Accessor<Zero<any, any, any>> | undefined\n>(undefined);\n\n/**\n * @deprecated Use {@link ZeroProvider} instead of managing your own Zero instance.\n */\nexport function createZero<\n S extends BaseDefaultSchema = DefaultSchema,\n MD extends CustomMutatorDefs | undefined = undefined,\n Context extends BaseDefaultContext = DefaultContext,\n>(options: ZeroOptions<S, MD, Context>): Zero<S, MD, Context> {\n const opts = {\n ...options,\n batchViewUpdates: batch,\n };\n return new Zero<S, MD, Context>(opts);\n}\n\nexport function useZero<\n S extends BaseDefaultSchema = DefaultSchema,\n MD extends CustomMutatorDefs | undefined = undefined,\n Context extends BaseDefaultContext = DefaultContext,\n>(): () => Zero<S, MD, Context> {\n const zero = useContext(ZeroContext);\n\n if (zero === undefined) {\n throw new Error('useZero must be used within a ZeroProvider');\n }\n return zero;\n}\n\n/**\n * @deprecated Use {@link useZero} instead, alongside default types defined with:\n *\n * ```ts\n * declare module '@rocicorp/zero' {\n * interface DefaultTypes {\n * schema: typeof schema;\n * context: Context;\n * }\n * }\n */\nexport function createUseZero<\n S extends BaseDefaultSchema = DefaultSchema,\n MD extends CustomMutatorDefs | undefined = undefined,\n Context extends BaseDefaultContext = DefaultContext,\n>() {\n return () => useZero<S, MD, Context>();\n}\n\nexport function ZeroProvider<\n S extends BaseDefaultSchema = DefaultSchema,\n MD extends CustomMutatorDefs | undefined = undefined,\n Context extends BaseDefaultContext = DefaultContext,\n>(\n props: {\n children: JSX.Element;\n /**\n * Called after ZeroProvider constructs a new Zero instance.\n *\n * This runs only when the provider creates Zero from options, and is not\n * called when an existing instance is passed with `zero`.\n */\n init?: (zero: Zero<S, MD, Context>) => void;\n } & (\n | {\n zero: Zero<S, MD, Context>;\n }\n | ZeroOptions<S, MD, Context>\n ),\n) {\n let prevAuth = 'auth' in props ? props.auth : undefined;\n const [rotationGeneration, setRotationGeneration] = createSignal(0);\n\n const auth = createMemo(() => ('auth' in props ? props.auth : undefined));\n const hasAuth = createMemo(() => typeof auth() === 'string');\n\n const zero = createMemo(() => {\n rotationGeneration();\n\n if ('zero' in props) {\n return props.zero;\n }\n\n hasAuth();\n\n const [local, options] = splitProps(props, ['children', 'auth', 'init']);\n\n const authValue = untrack(auth);\n prevAuth = authValue;\n let rotationRequested = false;\n\n const scheduleRotation = () => {\n if (rotationRequested) {\n return;\n }\n rotationRequested = true;\n setRotationGeneration(gen => gen + 1);\n };\n\n const createdZero = new Zero({\n ...options,\n ...(authValue !== undefined ? {auth: authValue} : {}),\n batchViewUpdates: batch,\n onClientStateNotFound: () => {\n if (rotationRequested) {\n return;\n }\n\n if (options.onClientStateNotFound) {\n try {\n options.onClientStateNotFound();\n return;\n } catch {\n // rotate since zero client is now closed\n }\n }\n\n scheduleRotation();\n },\n });\n local.init?.(createdZero);\n onCleanup(() => createdZero.close());\n return createdZero;\n });\n\n createEffect(() => {\n const currentZero = zero();\n if (!currentZero || 'zero' in props) {\n return;\n }\n\n const currentAuth = auth();\n\n if (currentAuth !== prevAuth) {\n const previousAuth = prevAuth;\n prevAuth = currentAuth;\n\n if (typeof previousAuth === 'string' && typeof currentAuth === 'string') {\n void currentZero.connection.connect({auth: currentAuth});\n }\n }\n });\n\n return ZeroContext.Provider({\n value: zero,\n get children() {\n return props.children;\n },\n });\n}\n"],"mappings":";;;;AAuBA,IAAM,cAAc,cAGlB,KAAA,CAAS;;;;AAKX,SAAgB,WAId,SAA4D;CAK5D,OAAO,IAAI,KAAqB;EAH9B,GAAG;EACH,kBAAkB;CAEY,CAAI;AACtC;AAEA,SAAgB,UAIgB;CAC9B,MAAM,OAAO,WAAW,WAAW;CAEnC,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,4CAA4C;CAE9D,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,gBAIZ;CACF,aAAa,QAAwB;AACvC;AAEA,SAAgB,aAKd,OAeA;CACA,IAAI,WAAW,UAAU,QAAQ,MAAM,OAAO,KAAA;CAC9C,MAAM,CAAC,oBAAoB,yBAAyB,aAAa,CAAC;CAElE,MAAM,OAAO,iBAAkB,UAAU,QAAQ,MAAM,OAAO,KAAA,CAAU;CACxE,MAAM,UAAU,iBAAiB,OAAO,KAAK,MAAM,QAAQ;CAE3D,MAAM,OAAO,iBAAiB;EAC5B,mBAAmB;EAEnB,IAAI,UAAU,OACZ,OAAO,MAAM;EAGf,QAAQ;EAER,MAAM,CAAC,OAAO,WAAW,WAAW,OAAO;GAAC;GAAY;GAAQ;EAAM,CAAC;EAEvE,MAAM,YAAY,QAAQ,IAAI;EAC9B,WAAW;EACX,IAAI,oBAAoB;EAExB,MAAM,yBAAyB;GAC7B,IAAI,mBACF;GAEF,oBAAoB;GACpB,uBAAsB,QAAO,MAAM,CAAC;EACtC;EAEA,MAAM,cAAc,IAAI,KAAK;GAC3B,GAAG;GACH,GAAI,cAAc,KAAA,IAAY,EAAC,MAAM,UAAS,IAAI,CAAC;GACnD,kBAAkB;GAClB,6BAA6B;IAC3B,IAAI,mBACF;IAGF,IAAI,QAAQ,uBACV,IAAI;KACF,QAAQ,sBAAsB;KAC9B;IACF,QAAQ,CAER;IAGF,iBAAiB;GACnB;EACF,CAAC;EACD,MAAM,OAAO,WAAW;EACxB,gBAAgB,YAAY,MAAM,CAAC;EACnC,OAAO;CACT,CAAC;CAED,mBAAmB;EACjB,MAAM,cAAc,KAAK;EACzB,IAAI,CAAC,eAAe,UAAU,OAC5B;EAGF,MAAM,cAAc,KAAK;EAEzB,IAAI,gBAAgB,UAAU;GAC5B,MAAM,eAAe;GACrB,WAAW;GAEX,IAAI,OAAO,iBAAiB,YAAY,OAAO,gBAAgB,UAC7D,YAAiB,WAAW,QAAQ,EAAC,MAAM,YAAW,CAAC;EAE3D;CACF,CAAC;CAED,OAAO,YAAY,SAAS;EAC1B,OAAO;EACP,IAAI,WAAW;GACb,OAAO,MAAM;EACf;CACF,CAAC;AACH"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChangeType } from './change-type.ts';
|
|
2
2
|
import type { Node } from './data.ts';
|
|
3
3
|
/**
|
|
4
|
-
* The `child` payload carried by a {@
|
|
4
|
+
* The `child` payload carried by a {@link ChildChange}.
|
|
5
5
|
*/
|
|
6
6
|
export type ChildData = {
|
|
7
7
|
relationshipName: string;
|
|
@@ -27,11 +27,11 @@ export type ChildChange = [
|
|
|
27
27
|
child: ChildData
|
|
28
28
|
];
|
|
29
29
|
/**
|
|
30
|
-
* The row changed (in a way that the {@
|
|
30
|
+
* The row changed (in a way that the {@link Source} determines). Most
|
|
31
31
|
* likely the PK stayed the same but there is really no restriction in how it
|
|
32
32
|
* can change.
|
|
33
33
|
*
|
|
34
|
-
* The edit changes flows down in a {@
|
|
34
|
+
* The edit changes flows down in a {@link Output.push}.
|
|
35
35
|
* There are cases where an edit change gets split into a remove and/or an add
|
|
36
36
|
* change.
|
|
37
37
|
* 1. when the presence of the row in the result changes (for example the row
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change.js","names":[],"sources":["../../../../../zql/src/ivm/change.ts"],"sourcesContent":["import {ChangeType} from './change-type.ts';\nimport type {Node} from './data.ts';\n\n/**\n * The `child` payload carried by a {@
|
|
1
|
+
{"version":3,"file":"change.js","names":[],"sources":["../../../../../zql/src/ivm/change.ts"],"sourcesContent":["import {ChangeType} from './change-type.ts';\nimport type {Node} from './data.ts';\n\n/**\n * The `child` payload carried by a {@link ChildChange}.\n */\nexport type ChildData = {\n relationshipName: string;\n change: Change;\n};\n\nexport type Change = AddChange | RemoveChange | ChildChange | EditChange;\n\n/**\n * Represents a node (and all its children) getting added to the result.\n */\nexport type AddChange = [type: ChangeType.ADD, node: Node, extra: null];\n\n/**\n * Represents a node (and all its children) getting removed from the result.\n */\nexport type RemoveChange = [type: ChangeType.REMOVE, node: Node, extra: null];\n\n/**\n * The node's row is unchanged, but one of its descendants has changed.\n * The node's relationships will reflect the change, `child` specifies the\n * specific descendant change.\n */\nexport type ChildChange = [\n type: ChangeType.CHILD,\n node: Node,\n child: ChildData,\n];\n\n/**\n * The row changed (in a way that the {@link Source} determines). Most\n * likely the PK stayed the same but there is really no restriction in how it\n * can change.\n *\n * The edit changes flows down in a {@link Output.push}.\n * There are cases where an edit change gets split into a remove and/or an add\n * change.\n * 1. when the presence of the row in the result changes (for example the row\n * is no longer present due to a filter)\n * 2. the edit results in the rows relationships changing\n *\n * If an edit is not split, the relationships of node and oldNode must\n * be the same, just the Row has changed.\n *\n * NOTE: It would be cleaner to just have the relationships once,\n * since they must be the same, however relationship Streams are single use\n * and if an Edit needs to be split into a remove and add a single map\n * of relationship Streams could not be used for the both the remove and\n * the add. This cleanup could be done if we move to multi-use Streams\n * for relationships.\n */\nexport type EditChange = [type: ChangeType.EDIT, node: Node, oldNode: Node];\n\n// Factory functions — prefer these over constructing tuple literals directly.\n\nexport function makeAddChange(node: Node): AddChange {\n return [ChangeType.ADD, node, null];\n}\n\nexport function makeRemoveChange(node: Node): RemoveChange {\n return [ChangeType.REMOVE, node, null];\n}\n\nexport function makeChildChange(node: Node, child: ChildData): ChildChange {\n return [ChangeType.CHILD, node, child];\n}\n\nexport function makeEditChange(node: Node, oldNode: Node): EditChange {\n return [ChangeType.EDIT, node, oldNode];\n}\n"],"mappings":";AA4DA,SAAgB,cAAc,MAAuB;CACnD,OAAO;EAAC;EAAgB;EAAM;CAAI;AACpC;AAEA,SAAgB,iBAAiB,MAA0B;CACzD,OAAO;EAAC;EAAmB;EAAM;CAAI;AACvC;AAEA,SAAgB,gBAAgB,MAAY,OAA+B;CACzE,OAAO;EAAC;EAAkB;EAAM;CAAK;AACvC;AAEA,SAAgB,eAAe,MAAY,SAA2B;CACpE,OAAO;EAAC;EAAiB;EAAM;CAAO;AACxC"}
|
|
@@ -10,7 +10,7 @@ export type Node = {
|
|
|
10
10
|
/**
|
|
11
11
|
* Relationships are generated lazily as read.
|
|
12
12
|
* The stream may contain 'yield' to indicate the operator has yielded control.
|
|
13
|
-
* See {@
|
|
13
|
+
* See {@link Operator.fetch} for more details about yields.
|
|
14
14
|
*/
|
|
15
15
|
relationships: Record<string, () => Stream<Node | 'yield'>>;
|
|
16
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.js","names":[],"sources":["../../../../../zql/src/ivm/data.ts"],"sourcesContent":["import {compareUTF8} from 'compare-utf8';\nimport type {Ordering} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {Stream} from './stream.ts';\n\n/**\n * A row flowing through the pipeline, plus its relationships.\n * Relationships are generated lazily as read.\n */\nexport type Node = {\n row: Row;\n /**\n * Relationships are generated lazily as read.\n * The stream may contain 'yield' to indicate the operator has yielded control.\n * See {@
|
|
1
|
+
{"version":3,"file":"data.js","names":[],"sources":["../../../../../zql/src/ivm/data.ts"],"sourcesContent":["import {compareUTF8} from 'compare-utf8';\nimport type {Ordering} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {Stream} from './stream.ts';\n\n/**\n * A row flowing through the pipeline, plus its relationships.\n * Relationships are generated lazily as read.\n */\nexport type Node = {\n row: Row;\n /**\n * Relationships are generated lazily as read.\n * The stream may contain 'yield' to indicate the operator has yielded control.\n * See {@link Operator.fetch} for more details about yields.\n */\n relationships: Record<string, () => Stream<Node | 'yield'>>;\n};\n\n/**\n * Compare two values. The values must be of the same type. This function\n * throws at runtime if the types differ.\n *\n * Note, this function considers `null === null` and\n * `undefined === undefined`. This is different than SQL. In join code,\n * null must be treated separately.\n *\n * See: https://github.com/rocicorp/mono/pull/2116/files#r1704811479\n *\n * @returns < 0 if a < b, 0 if a === b, > 0 if a > b\n */\nexport function compareValues(a: Value, b: Value): number {\n a = a ?? null;\n b = b ?? null;\n\n if (a === b) {\n return 0;\n }\n\n if (typeof a === 'string' && typeof b === 'string') {\n // We compare all strings in Zero as UTF-8. This is the default on SQLite\n // and we need to match it. See:\n // https://blog.replicache.dev/blog/replicache-11-adventures-in-text-encoding.\n //\n // TODO: We could change this since SQLite supports UTF-16. Microbenchmark\n // to see if there's a big win.\n //\n // https://www.sqlite.org/c3ref/create_collation.html\n return compareUTF8(a, b);\n }\n\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a ? 1 : -1;\n }\n\n // check with null after since it is less likely to be the common case and we\n // want to avoid the extra checks in that case\n if (a === null) {\n return -1;\n }\n if (b === null) {\n return 1;\n }\n\n if (typeof a !== typeof b) {\n throw new Error(\n `Cannot compare values of different types: ${typeof a} and ${typeof b}`,\n );\n }\n\n throw new Error(`Unsupported type: ${typeof a}`);\n}\n\nexport type NormalizedValue = Exclude<Value, undefined>;\n\n/**\n * We allow undefined to be passed for the convenience of developers, but we\n * treat it equivalently to null. It's better for perf to not create an copy\n * of input values, so we just normalize at use when necessary.\n */\nexport function normalizeUndefined(v: Value): NormalizedValue {\n return v ?? null;\n}\n\nexport type Comparator = (r1: Row, r2: Row) => number;\n\nexport function makeComparator(order: Ordering, reverse?: boolean): Comparator {\n // A single ascending field is the common shape -- it is what every source\n // ordered by a single-column primary key gets -- and specializing it drops\n // the loop, the direction test and the reverse test from the hottest\n // function in hydration.\n if (order.length === 1 && order[0][1] === 'asc' && !reverse) {\n const field = order[0][0];\n return (a, b) => compareValues(a[field], b[field]);\n }\n\n const length = order.length;\n return (a, b) => {\n // Skip destructuring here since it is hot code. An indexed loop rather\n // than `for...of` for the same reason: Hermes allocates an iterator and\n // calls `next()` per element, which costs more than the comparison for an\n // ordering this short.\n for (let i = 0; i < length; i++) {\n const ord = order[i];\n const comp = compareValues(a[ord[0]], b[ord[0]]);\n if (comp !== 0) {\n const result = ord[1] === 'asc' ? comp : -comp;\n return reverse ? -result : result;\n }\n }\n return 0;\n };\n}\n\n/**\n * Determine if two values are equal. Note that unlike compareValues() above,\n * this function treats `null` as unequal to itself (and same for `undefined`).\n * This is required to make joins work correctly, but may not be the right\n * semantic for your application.\n */\nexport function valuesEqual(a: Value, b: Value): boolean {\n // oxlint-disable-next-line eqeqeq\n if (a == null || b == null) {\n return false;\n }\n return a === b;\n}\n\nexport function drainStreams(node: Node | 'yield') {\n if (node === 'yield') {\n return;\n }\n for (const stream of Object.values(node.relationships)) {\n for (const node of stream()) {\n drainStreams(node);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA+BA,SAAgB,cAAc,GAAU,GAAkB;CACxD,IAAI,KAAK;CACT,IAAI,KAAK;CAET,IAAI,MAAM,GACR,OAAO;CAGT,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UASxC,OAAO,YAAY,GAAG,CAAC;CAGzB,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UACxC,OAAO,IAAI;CAGb,IAAI,OAAO,MAAM,aAAa,OAAO,MAAM,WACzC,OAAO,IAAI,IAAI;CAKjB,IAAI,MAAM,MACR,OAAO;CAET,IAAI,MAAM,MACR,OAAO;CAGT,IAAI,OAAO,MAAM,OAAO,GACtB,MAAM,IAAI,MACR,6CAA6C,OAAO,EAAE,OAAO,OAAO,GACtE;CAGF,MAAM,IAAI,MAAM,qBAAqB,OAAO,GAAG;AACjD;;;;;;AASA,SAAgB,mBAAmB,GAA2B;CAC5D,OAAO,KAAK;AACd;AAIA,SAAgB,eAAe,OAAiB,SAA+B;CAK7E,IAAI,MAAM,WAAW,KAAK,MAAM,GAAG,OAAO,SAAS,CAAC,SAAS;EAC3D,MAAM,QAAQ,MAAM,GAAG;EACvB,QAAQ,GAAG,MAAM,cAAc,EAAE,QAAQ,EAAE,MAAM;CACnD;CAEA,MAAM,SAAS,MAAM;CACrB,QAAQ,GAAG,MAAM;EAKf,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;GAC/B,MAAM,MAAM,MAAM;GAClB,MAAM,OAAO,cAAc,EAAE,IAAI,KAAK,EAAE,IAAI,GAAG;GAC/C,IAAI,SAAS,GAAG;IACd,MAAM,SAAS,IAAI,OAAO,QAAQ,OAAO,CAAC;IAC1C,OAAO,UAAU,CAAC,SAAS;GAC7B;EACF;EACA,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,YAAY,GAAU,GAAmB;CAEvD,IAAI,KAAK,QAAQ,KAAK,MACpB,OAAO;CAET,OAAO,MAAM;AACf"}
|