@rocicorp/zero 1.11.0-canary.41 → 1.11.0-canary.43
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/services/change-streamer/change-streamer-http.d.ts +3 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +4 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +3 -3
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts +4 -4
- package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/drain-tracker.js +4 -8
- package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
- package/out/zero-cache/src/services/http-service.d.ts +6 -0
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +11 -0
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +3 -2
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +17 -4
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/replicator/drain-handler.d.ts +8 -0
- package/out/zero-cache/src/services/replicator/drain-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/drain-handler.js +38 -4
- package/out/zero-cache/src/services/replicator/drain-handler.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":"connection-manager.js","names":["#disconnectTimeout","#timeoutCheckIntervalMs","#state","#lastTimeoutCheckAt","#nextStatePromise","#connectRequestResolver","#consumeConnectRequest","#connectingStartedAt","#maybeStartTimeoutInterval","#publishStateAndGetPromise","#maybeStopTimeoutInterval","#publishState","#resolveNextStateWaiters","#stateChangeResolver","#timeoutInterval","#creditFrozenTime","#checkTimeout"],"sources":["../../../../../zero-client/src/client/connection-manager.ts"],"sourcesContent":["import {resolver, type Resolver} from '@rocicorp/resolver';\nimport {Subscribable} from '../../../shared/src/subscribable.ts';\nimport {ClientErrorKind} from './client-error-kind.ts';\nimport {ConnectionStatus} from './connection-status.ts';\nimport {\n ClientError,\n isClientError,\n type AuthError,\n type ClosedError,\n type DisconnectedReason,\n type ZeroError,\n} from './error.ts';\n\nconst DEFAULT_TIMEOUT_CHECK_INTERVAL_MS = 1_000;\n\nexport type ConnectionManagerState =\n | {\n name: ConnectionStatus.Initializing;\n }\n | {\n name: ConnectionStatus.Disconnected;\n reason: DisconnectedReason;\n }\n | {\n name: ConnectionStatus.Connecting;\n attempt: number;\n disconnectAt: number;\n reason?: ZeroError | undefined;\n }\n | {\n name: ConnectionStatus.Connected;\n }\n | {\n name: ConnectionStatus.NeedsAuth;\n reason: AuthError;\n }\n | {\n name: ConnectionStatus.Error;\n reason: ZeroError;\n }\n | {\n name: ConnectionStatus.Closed;\n reason: ClosedError;\n };\n\nexport type ConnectionManagerOptions = {\n /**\n * The amount of milliseconds we allow for continuous connecting attempts before\n * transitioning to disconnected state.\n */\n disconnectTimeout: number;\n /**\n * How frequently we check whether the connecting timeout has elapsed.\n * Defaults to 1 second.\n */\n timeoutCheckIntervalMs?: number | undefined;\n};\n\nconst TERMINAL_STATES = [\n ConnectionStatus.NeedsAuth,\n ConnectionStatus.Error,\n] as const satisfies ConnectionStatus[];\n\ntype TerminalConnectionStatus = (typeof TERMINAL_STATES)[number];\ntype TerminalConnectionManagerState = Extract<\n ConnectionManagerState,\n {name: TerminalConnectionStatus}\n>;\n\nexport class ConnectionManager extends Subscribable<ConnectionManagerState> {\n #state: ConnectionManagerState;\n #connectRequestResolver: Resolver<void> | undefined = resolver();\n\n /**\n * The timestamp when we first started trying to connect.\n * This is used to track the retry window.\n * Reset to undefined when we successfully connect or when we transition to disconnected.\n */\n #connectingStartedAt: number | undefined;\n\n /**\n * The amount of milliseconds we allow for continuous connecting attempts before\n * transitioning to disconnected state.\n */\n #disconnectTimeout: number;\n\n /**\n * Handle for the timeout interval that periodically checks whether we've\n * exceeded the allowed connecting window.\n */\n #timeoutInterval: ReturnType<typeof setInterval> | undefined;\n\n /**\n * Interval duration for checking whether the connecting timeout has elapsed.\n */\n #timeoutCheckIntervalMs: number;\n\n /**\n * `Date.now()` at the last timeout-interval tick, used to notice that the\n * JavaScript event loop was not running. See {@linkcode #creditFrozenTime}.\n */\n #lastTimeoutCheckAt: number;\n\n /**\n * Resolver used to signal waiting callers when the state changes.\n */\n #stateChangeResolver: Resolver<ConnectionManagerState> = resolver();\n\n constructor(options: ConnectionManagerOptions) {\n super();\n\n this.#disconnectTimeout = options.disconnectTimeout;\n this.#timeoutCheckIntervalMs =\n options.timeoutCheckIntervalMs ?? DEFAULT_TIMEOUT_CHECK_INTERVAL_MS;\n // The connecting window, and the timer that enforces it, start in\n // initialized(), once the local store is loaded.\n this.#state = {name: ConnectionStatus.Initializing};\n this.#lastTimeoutCheckAt = Date.now();\n }\n\n get state(): ConnectionManagerState {\n return this.#state;\n }\n\n /**\n * Returns true if the current state is equal to the given status.\n */\n is(status: ConnectionStatus): boolean {\n return this.#state.name === status;\n }\n\n /**\n * Returns true if the current state is a terminal state\n * that can be recovered from by calling connect().\n */\n isInTerminalState(): boolean {\n return ConnectionManager.isTerminalState(this.#state);\n }\n\n /**\n * Returns true if the given status is a terminal state\n * that can be recovered from by calling connect().\n */\n static isTerminalState(\n state: ConnectionManagerState,\n ): state is TerminalConnectionManagerState {\n return (TERMINAL_STATES as readonly ConnectionStatus[]).includes(\n state.name,\n );\n }\n\n /**\n * Returns true if the run loop should continue.\n * The run loop continues in all states except closed.\n * In needs-auth and error states, the run loop pauses and waits for connect() to be called.\n */\n shouldContinueRunLoop(): boolean {\n return this.#state.name !== ConnectionStatus.Closed;\n }\n\n /**\n * Waits for the next state change.\n * @returns A promise that resolves when the next state change occurs.\n */\n waitForStateChange(): Promise<ConnectionManagerState> {\n return this.#nextStatePromise();\n }\n\n requestConnect(): void {\n if (this.#connectRequestResolver === undefined) {\n return;\n }\n this.#connectRequestResolver.resolve();\n this.#connectRequestResolver = undefined;\n }\n\n waitForConnectRequest(): Promise<void> {\n return this.#connectRequestResolver === undefined\n ? Promise.resolve()\n : this.#connectRequestResolver.promise;\n }\n\n #consumeConnectRequest(): boolean {\n if (this.#connectRequestResolver !== undefined) {\n return false;\n }\n this.#connectRequestResolver = resolver();\n return true;\n }\n\n /**\n * Consume a pending connect request and resume connecting.\n *\n * @returns true if a pending request was handled.\n */\n resumeFromConnectRequest(): boolean {\n if (!this.isInTerminalState()) {\n return false;\n }\n if (!this.#consumeConnectRequest()) {\n return false;\n }\n this.connecting();\n return true;\n }\n\n /**\n * Transition from initializing to connecting, once the local store is\n * loaded.\n *\n * This is where the connecting window starts. Loading the local store can\n * take tens of seconds on a slow device with a large replica, and none of\n * that says anything about whether the server is reachable, so it is not\n * charged against the disconnect timeout. No connect attempt has been made\n * yet, so `attempt` is 0.\n *\n * A no-op in any other state: something else, like `close()`, has already\n * moved the state on.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n initialized(): {nextStatePromise: Promise<ConnectionManagerState>} {\n if (this.#state.name !== ConnectionStatus.Initializing) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n const now = Date.now();\n this.#connectingStartedAt = now;\n this.#state = {\n name: ConnectionStatus.Connecting,\n attempt: 0,\n disconnectAt: now + this.#disconnectTimeout,\n };\n // Start the interval before publishing: a subscriber may close the manager\n // synchronously, and closed() can only stop an interval that exists.\n this.#maybeStartTimeoutInterval();\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to connecting state.\n *\n * This starts the timeout timer, but if we've entered disconnected state,\n * we stay there and continue retrying.\n *\n * If we're disconnected due to a hidden tab, allow a transition back to\n * connecting when visibility returns.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n connecting(reason?: ZeroError): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n const isHiddenDisconnect =\n this.#state.name === ConnectionStatus.Disconnected &&\n this.#state.reason.kind === ClientErrorKind.Hidden;\n\n // we cannot intentionally transition from disconnected to connecting\n // unless the disconnection was due to a hidden tab\n if (\n this.#state.name === ConnectionStatus.Disconnected &&\n !isHiddenDisconnect\n ) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n const now = Date.now();\n\n if (isHiddenDisconnect) {\n // Reset the retry window after a hidden-tab disconnect.\n this.#connectingStartedAt = undefined;\n }\n\n // If we're already connecting, increment the attempt counter\n if (this.#state.name === ConnectionStatus.Connecting) {\n this.#state = {\n ...this.#state,\n attempt: this.#state.attempt + 1,\n reason,\n };\n // See initialized() for why the interval starts before publishing.\n this.#maybeStartTimeoutInterval();\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n // Starting a new connecting session\n // If #connectingStartedAt is undefined, this is a fresh start - set it to now\n // If it's already set, we're retrying within the same retry window, so keep it\n if (this.#connectingStartedAt === undefined) {\n this.#connectingStartedAt = now;\n }\n\n const disconnectAt = this.#connectingStartedAt + this.#disconnectTimeout;\n\n this.#state = {\n name: ConnectionStatus.Connecting,\n attempt: 1,\n disconnectAt,\n reason,\n };\n // Start the interval before publishing: a subscriber may close the manager\n // synchronously, and closed() can only stop an interval that exists.\n this.#maybeStartTimeoutInterval();\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to connected state.\n * This resets the connecting timeout timer.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n connected(): {nextStatePromise: Promise<ConnectionManagerState>} {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already connected, no-op\n if (this.#state.name === ConnectionStatus.Connected) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Reset the timeout timer on successful connection\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Connected,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to disconnected state.\n * This is called when the timeout expires.\n * The run loop will continue trying to reconnect.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n disconnected(reason: DisconnectedReason): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already disconnected, no-op\n if (this.#state.name === ConnectionStatus.Disconnected) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // When transitioning from connected to disconnected, we've lost a connection\n // we previously had. Clear the timeout timer so we can start a fresh timeout window.\n if (this.#state.name === ConnectionStatus.Connected) {\n this.#connectingStartedAt = undefined;\n }\n // When transitioning from connecting to disconnected (e.g., due to timeout),\n // we keep the start time to maintain the context that we've been trying for a while.\n\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Disconnected,\n reason,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to needs-auth state.\n * This pauses the run loop until connect() is called with new credentials.\n * Resets the retry window and attempt counter.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n needsAuth(reason: AuthError): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already in needs-auth state, no-op\n if (this.#state.name === ConnectionStatus.NeedsAuth) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Reset the timeout timer and connecting start time\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.NeedsAuth,\n reason,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to error state.\n * This pauses the run loop until connect() is called.\n * Resets the retry window and attempt counter.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n error(reason: ZeroError): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already in error state, no-op\n if (this.#state.name === ConnectionStatus.Error) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Reset the timeout timer and connecting start time\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Error,\n reason,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to closed state.\n * This is terminal - no further transitions are allowed.\n */\n closed() {\n // Already closed, no-op\n if (this.#state.name === ConnectionStatus.Closed) {\n return;\n }\n\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Closed,\n reason: new ClientError({\n kind: ClientErrorKind.ClientClosed,\n message: 'Zero was explicitly closed by calling zero.close()',\n }),\n };\n this.#publishState();\n this.cleanup();\n return;\n }\n\n override cleanup = (): void => {\n this._listeners.clear();\n this.#resolveNextStateWaiters();\n };\n\n #resolveNextStateWaiters(): void {\n this.#stateChangeResolver.resolve(this.#state);\n this.#stateChangeResolver = resolver();\n }\n\n #publishState(): void {\n this.notify(this.#state);\n this.#resolveNextStateWaiters();\n }\n\n #nextStatePromise(): Promise<ConnectionManagerState> {\n return this.#stateChangeResolver.promise;\n }\n\n #publishStateAndGetPromise(): Promise<ConnectionManagerState> {\n this.#publishState();\n return this.#nextStatePromise();\n }\n\n /**\n * Pushes the disconnect deadline forward by however long the event loop was\n * not running.\n *\n * `disconnectAt` is an absolute wall-clock instant, but what it is meant to\n * bound is time spent *actually retrying*. Whenever the loop is frozen --- a\n * suspended React Native app, a sleeping laptop, a throttled background tab\n * --- wall-clock keeps advancing while no reconnect attempt can run, so\n * without this the first tick after resume reports `Offline` having given the\n * client zero live seconds to connect.\n *\n * This interval is its own detector: a tick that lands late by more than a\n * full period means we were not running in between. A merely busy JS thread\n * can also delay a tick, and we cannot tell the two apart from lateness\n * alone, but the costs are asymmetric --- crediting a busy period back just\n * grants a bit more time to connect, whereas failing to credit a real freeze\n * produces a user-visible bogus disconnect --- so we credit.\n *\n * The whole gap is credited, not `elapsed` minus a period. An overdue\n * `setInterval` callback runs as soon as the loop resumes rather than waiting\n * out another period, so `elapsed` is already about the frozen duration;\n * netting off a period would charge up to a full period of frozen time\n * against the deadline. Freezing just after a tick would then leave the\n * deadline exactly at `now` on resume and disconnect immediately, which is\n * the very thing this is here to prevent.\n */\n #creditFrozenTime(): void {\n const now = Date.now();\n const elapsed = now - this.#lastTimeoutCheckAt;\n this.#lastTimeoutCheckAt = now;\n\n // Ordinary scheduling jitter, not a freeze.\n if (elapsed <= 2 * this.#timeoutCheckIntervalMs) {\n return;\n }\n const frozenMs = elapsed;\n\n // Advance the window's origin so that a later `connecting()` that starts a\n // fresh session recomputes a deadline that also excludes the frozen time.\n if (this.#connectingStartedAt !== undefined) {\n this.#connectingStartedAt += frozenMs;\n }\n\n if (this.#state.name === ConnectionStatus.Connecting) {\n // Deliberately not published: the status has not changed, and waking the\n // run loop's `waitForStateChange` racers here would interrupt an\n // in-flight connect attempt for no reason. Readers of `state` still see\n // the corrected deadline.\n this.#state = {\n ...this.#state,\n disconnectAt: this.#state.disconnectAt + frozenMs,\n };\n }\n }\n\n /**\n * Check if we should transition from connecting to disconnected due to timeout.\n * Returns true if the transition happened.\n */\n #checkTimeout(): boolean {\n if (this.#state.name !== ConnectionStatus.Connecting) {\n return false;\n }\n\n const now = Date.now();\n if (now >= this.#state.disconnectAt) {\n this.disconnected(\n new ClientError({\n kind: ClientErrorKind.Offline,\n message: `Zero was unable to connect for ${Math.floor(this.#disconnectTimeout / 1_000)} seconds and was disconnected`,\n }),\n );\n return true;\n }\n\n return false;\n }\n\n #maybeStartTimeoutInterval(): void {\n if (this.#timeoutInterval !== undefined) {\n return;\n }\n this.#lastTimeoutCheckAt = Date.now();\n this.#timeoutInterval = setInterval(() => {\n this.#creditFrozenTime();\n this.#checkTimeout();\n }, this.#timeoutCheckIntervalMs);\n }\n\n #maybeStopTimeoutInterval(): void {\n if (this.#timeoutInterval === undefined) {\n return;\n }\n clearInterval(this.#timeoutInterval);\n this.#timeoutInterval = undefined;\n }\n}\n\n/**\n * Used to trigger the catch block when a terminal state is reached.\n *\n * @param state - The current connection state.\n */\nexport const throwIfConnectionError = (state: ConnectionManagerState) => {\n if (\n ConnectionManager.isTerminalState(state) ||\n state.name === ConnectionStatus.Closed ||\n ((state.name === ConnectionStatus.Connecting ||\n state.name === ConnectionStatus.Disconnected) &&\n state.reason)\n ) {\n if (\n isClientError(state.reason) &&\n (state.reason.kind === ClientErrorKind.ConnectTimeout ||\n state.reason.kind === ClientErrorKind.AbruptClose ||\n state.reason.kind === ClientErrorKind.CleanClose ||\n state.reason.kind === ClientErrorKind.Hidden)\n ) {\n return;\n }\n throw state.reason;\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connection-manager.js","names":["#disconnectTimeout","#timeoutCheckIntervalMs","#state","#lastTimeoutCheckAt","#nextStatePromise","#connectRequestResolver","#consumeConnectRequest","#connectingStartedAt","#maybeStartTimeoutInterval","#publishStateAndGetPromise","#maybeStopTimeoutInterval","#publishState","#resolveNextStateWaiters","#stateChangeResolver","#timeoutInterval","#creditFrozenTime","#checkTimeout"],"sources":["../../../../../zero-client/src/client/connection-manager.ts"],"sourcesContent":["import {resolver, type Resolver} from '@rocicorp/resolver';\nimport {Subscribable} from '../../../shared/src/subscribable.ts';\nimport {ClientErrorKind} from './client-error-kind.ts';\nimport {ConnectionStatus} from './connection-status.ts';\nimport {\n ClientError,\n isClientError,\n type AuthError,\n type ClosedError,\n type DisconnectedReason,\n type ZeroError,\n} from './error.ts';\n\nconst DEFAULT_TIMEOUT_CHECK_INTERVAL_MS = 1_000;\n\nexport type ConnectionManagerState =\n | {\n name: ConnectionStatus.Initializing;\n }\n | {\n name: ConnectionStatus.Disconnected;\n reason: DisconnectedReason;\n }\n | {\n name: ConnectionStatus.Connecting;\n attempt: number;\n disconnectAt: number;\n reason?: ZeroError | undefined;\n }\n | {\n name: ConnectionStatus.Connected;\n }\n | {\n name: ConnectionStatus.NeedsAuth;\n reason: AuthError;\n }\n | {\n name: ConnectionStatus.Error;\n reason: ZeroError;\n }\n | {\n name: ConnectionStatus.Closed;\n reason: ClosedError;\n };\n\nexport type ConnectionManagerOptions = {\n /**\n * The amount of milliseconds we allow for continuous connecting attempts before\n * transitioning to disconnected state.\n */\n disconnectTimeout: number;\n /**\n * How frequently we check whether the connecting timeout has elapsed.\n * Defaults to 1 second.\n */\n timeoutCheckIntervalMs?: number | undefined;\n};\n\nconst TERMINAL_STATES = [\n ConnectionStatus.NeedsAuth,\n ConnectionStatus.Error,\n] as const satisfies ConnectionStatus[];\n\ntype TerminalConnectionStatus = (typeof TERMINAL_STATES)[number];\ntype TerminalConnectionManagerState = Extract<\n ConnectionManagerState,\n {name: TerminalConnectionStatus}\n>;\n\nexport class ConnectionManager extends Subscribable<ConnectionManagerState> {\n #state: ConnectionManagerState;\n #connectRequestResolver: Resolver<void> | undefined = resolver();\n\n /**\n * The timestamp when we first started trying to connect.\n * This is used to track the retry window.\n * Reset to undefined when we successfully connect or when we transition to disconnected.\n */\n #connectingStartedAt: number | undefined;\n\n /**\n * The amount of milliseconds we allow for continuous connecting attempts before\n * transitioning to disconnected state.\n */\n #disconnectTimeout: number;\n\n /**\n * Handle for the timeout interval that periodically checks whether we've\n * exceeded the allowed connecting window.\n */\n #timeoutInterval: ReturnType<typeof setInterval> | undefined;\n\n /**\n * Interval duration for checking whether the connecting timeout has elapsed.\n */\n #timeoutCheckIntervalMs: number;\n\n /**\n * `Date.now()` at the last timeout-interval tick, used to notice that the\n * JavaScript event loop was not running. See {@link #creditFrozenTime}.\n */\n #lastTimeoutCheckAt: number;\n\n /**\n * Resolver used to signal waiting callers when the state changes.\n */\n #stateChangeResolver: Resolver<ConnectionManagerState> = resolver();\n\n constructor(options: ConnectionManagerOptions) {\n super();\n\n this.#disconnectTimeout = options.disconnectTimeout;\n this.#timeoutCheckIntervalMs =\n options.timeoutCheckIntervalMs ?? DEFAULT_TIMEOUT_CHECK_INTERVAL_MS;\n // The connecting window, and the timer that enforces it, start in\n // initialized(), once the local store is loaded.\n this.#state = {name: ConnectionStatus.Initializing};\n this.#lastTimeoutCheckAt = Date.now();\n }\n\n get state(): ConnectionManagerState {\n return this.#state;\n }\n\n /**\n * Returns true if the current state is equal to the given status.\n */\n is(status: ConnectionStatus): boolean {\n return this.#state.name === status;\n }\n\n /**\n * Returns true if the current state is a terminal state\n * that can be recovered from by calling connect().\n */\n isInTerminalState(): boolean {\n return ConnectionManager.isTerminalState(this.#state);\n }\n\n /**\n * Returns true if the given status is a terminal state\n * that can be recovered from by calling connect().\n */\n static isTerminalState(\n state: ConnectionManagerState,\n ): state is TerminalConnectionManagerState {\n return (TERMINAL_STATES as readonly ConnectionStatus[]).includes(\n state.name,\n );\n }\n\n /**\n * Returns true if the run loop should continue.\n * The run loop continues in all states except closed.\n * In needs-auth and error states, the run loop pauses and waits for connect() to be called.\n */\n shouldContinueRunLoop(): boolean {\n return this.#state.name !== ConnectionStatus.Closed;\n }\n\n /**\n * Waits for the next state change.\n * @returns A promise that resolves when the next state change occurs.\n */\n waitForStateChange(): Promise<ConnectionManagerState> {\n return this.#nextStatePromise();\n }\n\n requestConnect(): void {\n if (this.#connectRequestResolver === undefined) {\n return;\n }\n this.#connectRequestResolver.resolve();\n this.#connectRequestResolver = undefined;\n }\n\n waitForConnectRequest(): Promise<void> {\n return this.#connectRequestResolver === undefined\n ? Promise.resolve()\n : this.#connectRequestResolver.promise;\n }\n\n #consumeConnectRequest(): boolean {\n if (this.#connectRequestResolver !== undefined) {\n return false;\n }\n this.#connectRequestResolver = resolver();\n return true;\n }\n\n /**\n * Consume a pending connect request and resume connecting.\n *\n * @returns true if a pending request was handled.\n */\n resumeFromConnectRequest(): boolean {\n if (!this.isInTerminalState()) {\n return false;\n }\n if (!this.#consumeConnectRequest()) {\n return false;\n }\n this.connecting();\n return true;\n }\n\n /**\n * Transition from initializing to connecting, once the local store is\n * loaded.\n *\n * This is where the connecting window starts. Loading the local store can\n * take tens of seconds on a slow device with a large replica, and none of\n * that says anything about whether the server is reachable, so it is not\n * charged against the disconnect timeout. No connect attempt has been made\n * yet, so `attempt` is 0.\n *\n * A no-op in any other state: something else, like `close()`, has already\n * moved the state on.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n initialized(): {nextStatePromise: Promise<ConnectionManagerState>} {\n if (this.#state.name !== ConnectionStatus.Initializing) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n const now = Date.now();\n this.#connectingStartedAt = now;\n this.#state = {\n name: ConnectionStatus.Connecting,\n attempt: 0,\n disconnectAt: now + this.#disconnectTimeout,\n };\n // Start the interval before publishing: a subscriber may close the manager\n // synchronously, and closed() can only stop an interval that exists.\n this.#maybeStartTimeoutInterval();\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to connecting state.\n *\n * This starts the timeout timer, but if we've entered disconnected state,\n * we stay there and continue retrying.\n *\n * If we're disconnected due to a hidden tab, allow a transition back to\n * connecting when visibility returns.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n connecting(reason?: ZeroError): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n const isHiddenDisconnect =\n this.#state.name === ConnectionStatus.Disconnected &&\n this.#state.reason.kind === ClientErrorKind.Hidden;\n\n // we cannot intentionally transition from disconnected to connecting\n // unless the disconnection was due to a hidden tab\n if (\n this.#state.name === ConnectionStatus.Disconnected &&\n !isHiddenDisconnect\n ) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n const now = Date.now();\n\n if (isHiddenDisconnect) {\n // Reset the retry window after a hidden-tab disconnect.\n this.#connectingStartedAt = undefined;\n }\n\n // If we're already connecting, increment the attempt counter\n if (this.#state.name === ConnectionStatus.Connecting) {\n this.#state = {\n ...this.#state,\n attempt: this.#state.attempt + 1,\n reason,\n };\n // See initialized() for why the interval starts before publishing.\n this.#maybeStartTimeoutInterval();\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n // Starting a new connecting session\n // If #connectingStartedAt is undefined, this is a fresh start - set it to now\n // If it's already set, we're retrying within the same retry window, so keep it\n if (this.#connectingStartedAt === undefined) {\n this.#connectingStartedAt = now;\n }\n\n const disconnectAt = this.#connectingStartedAt + this.#disconnectTimeout;\n\n this.#state = {\n name: ConnectionStatus.Connecting,\n attempt: 1,\n disconnectAt,\n reason,\n };\n // Start the interval before publishing: a subscriber may close the manager\n // synchronously, and closed() can only stop an interval that exists.\n this.#maybeStartTimeoutInterval();\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to connected state.\n * This resets the connecting timeout timer.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n connected(): {nextStatePromise: Promise<ConnectionManagerState>} {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already connected, no-op\n if (this.#state.name === ConnectionStatus.Connected) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Reset the timeout timer on successful connection\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Connected,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to disconnected state.\n * This is called when the timeout expires.\n * The run loop will continue trying to reconnect.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n disconnected(reason: DisconnectedReason): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already disconnected, no-op\n if (this.#state.name === ConnectionStatus.Disconnected) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // When transitioning from connected to disconnected, we've lost a connection\n // we previously had. Clear the timeout timer so we can start a fresh timeout window.\n if (this.#state.name === ConnectionStatus.Connected) {\n this.#connectingStartedAt = undefined;\n }\n // When transitioning from connecting to disconnected (e.g., due to timeout),\n // we keep the start time to maintain the context that we've been trying for a while.\n\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Disconnected,\n reason,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to needs-auth state.\n * This pauses the run loop until connect() is called with new credentials.\n * Resets the retry window and attempt counter.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n needsAuth(reason: AuthError): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already in needs-auth state, no-op\n if (this.#state.name === ConnectionStatus.NeedsAuth) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Reset the timeout timer and connecting start time\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.NeedsAuth,\n reason,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to error state.\n * This pauses the run loop until connect() is called.\n * Resets the retry window and attempt counter.\n *\n * @returns An object containing a promise that resolves on the next state change.\n */\n error(reason: ZeroError): {\n nextStatePromise: Promise<ConnectionManagerState>;\n } {\n // cannot transition from closed to any other status\n if (this.#state.name === ConnectionStatus.Closed) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Already in error state, no-op\n if (this.#state.name === ConnectionStatus.Error) {\n return {nextStatePromise: this.#nextStatePromise()};\n }\n\n // Reset the timeout timer and connecting start time\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Error,\n reason,\n };\n const nextStatePromise = this.#publishStateAndGetPromise();\n return {nextStatePromise};\n }\n\n /**\n * Transition to closed state.\n * This is terminal - no further transitions are allowed.\n */\n closed() {\n // Already closed, no-op\n if (this.#state.name === ConnectionStatus.Closed) {\n return;\n }\n\n this.#connectingStartedAt = undefined;\n this.#maybeStopTimeoutInterval();\n\n this.#state = {\n name: ConnectionStatus.Closed,\n reason: new ClientError({\n kind: ClientErrorKind.ClientClosed,\n message: 'Zero was explicitly closed by calling zero.close()',\n }),\n };\n this.#publishState();\n this.cleanup();\n return;\n }\n\n override cleanup = (): void => {\n this._listeners.clear();\n this.#resolveNextStateWaiters();\n };\n\n #resolveNextStateWaiters(): void {\n this.#stateChangeResolver.resolve(this.#state);\n this.#stateChangeResolver = resolver();\n }\n\n #publishState(): void {\n this.notify(this.#state);\n this.#resolveNextStateWaiters();\n }\n\n #nextStatePromise(): Promise<ConnectionManagerState> {\n return this.#stateChangeResolver.promise;\n }\n\n #publishStateAndGetPromise(): Promise<ConnectionManagerState> {\n this.#publishState();\n return this.#nextStatePromise();\n }\n\n /**\n * Pushes the disconnect deadline forward by however long the event loop was\n * not running.\n *\n * `disconnectAt` is an absolute wall-clock instant, but what it is meant to\n * bound is time spent *actually retrying*. Whenever the loop is frozen --- a\n * suspended React Native app, a sleeping laptop, a throttled background tab\n * --- wall-clock keeps advancing while no reconnect attempt can run, so\n * without this the first tick after resume reports `Offline` having given the\n * client zero live seconds to connect.\n *\n * This interval is its own detector: a tick that lands late by more than a\n * full period means we were not running in between. A merely busy JS thread\n * can also delay a tick, and we cannot tell the two apart from lateness\n * alone, but the costs are asymmetric --- crediting a busy period back just\n * grants a bit more time to connect, whereas failing to credit a real freeze\n * produces a user-visible bogus disconnect --- so we credit.\n *\n * The whole gap is credited, not `elapsed` minus a period. An overdue\n * `setInterval` callback runs as soon as the loop resumes rather than waiting\n * out another period, so `elapsed` is already about the frozen duration;\n * netting off a period would charge up to a full period of frozen time\n * against the deadline. Freezing just after a tick would then leave the\n * deadline exactly at `now` on resume and disconnect immediately, which is\n * the very thing this is here to prevent.\n */\n #creditFrozenTime(): void {\n const now = Date.now();\n const elapsed = now - this.#lastTimeoutCheckAt;\n this.#lastTimeoutCheckAt = now;\n\n // Ordinary scheduling jitter, not a freeze.\n if (elapsed <= 2 * this.#timeoutCheckIntervalMs) {\n return;\n }\n const frozenMs = elapsed;\n\n // Advance the window's origin so that a later `connecting()` that starts a\n // fresh session recomputes a deadline that also excludes the frozen time.\n if (this.#connectingStartedAt !== undefined) {\n this.#connectingStartedAt += frozenMs;\n }\n\n if (this.#state.name === ConnectionStatus.Connecting) {\n // Deliberately not published: the status has not changed, and waking the\n // run loop's `waitForStateChange` racers here would interrupt an\n // in-flight connect attempt for no reason. Readers of `state` still see\n // the corrected deadline.\n this.#state = {\n ...this.#state,\n disconnectAt: this.#state.disconnectAt + frozenMs,\n };\n }\n }\n\n /**\n * Check if we should transition from connecting to disconnected due to timeout.\n * Returns true if the transition happened.\n */\n #checkTimeout(): boolean {\n if (this.#state.name !== ConnectionStatus.Connecting) {\n return false;\n }\n\n const now = Date.now();\n if (now >= this.#state.disconnectAt) {\n this.disconnected(\n new ClientError({\n kind: ClientErrorKind.Offline,\n message: `Zero was unable to connect for ${Math.floor(this.#disconnectTimeout / 1_000)} seconds and was disconnected`,\n }),\n );\n return true;\n }\n\n return false;\n }\n\n #maybeStartTimeoutInterval(): void {\n if (this.#timeoutInterval !== undefined) {\n return;\n }\n this.#lastTimeoutCheckAt = Date.now();\n this.#timeoutInterval = setInterval(() => {\n this.#creditFrozenTime();\n this.#checkTimeout();\n }, this.#timeoutCheckIntervalMs);\n }\n\n #maybeStopTimeoutInterval(): void {\n if (this.#timeoutInterval === undefined) {\n return;\n }\n clearInterval(this.#timeoutInterval);\n this.#timeoutInterval = undefined;\n }\n}\n\n/**\n * Used to trigger the catch block when a terminal state is reached.\n *\n * @param state - The current connection state.\n */\nexport const throwIfConnectionError = (state: ConnectionManagerState) => {\n if (\n ConnectionManager.isTerminalState(state) ||\n state.name === ConnectionStatus.Closed ||\n ((state.name === ConnectionStatus.Connecting ||\n state.name === ConnectionStatus.Disconnected) &&\n state.reason)\n ) {\n if (\n isClientError(state.reason) &&\n (state.reason.kind === ClientErrorKind.ConnectTimeout ||\n state.reason.kind === ClientErrorKind.AbruptClose ||\n state.reason.kind === ClientErrorKind.CleanClose ||\n state.reason.kind === ClientErrorKind.Hidden)\n ) {\n return;\n }\n throw state.reason;\n }\n};\n"],"mappings":"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"}
|
|
@@ -58,7 +58,7 @@ export declare class TransactionImpl<TSchema extends Schema = DefaultSchema> imp
|
|
|
58
58
|
readonly location = "client";
|
|
59
59
|
readonly mutate: TransactionMutate<TSchema>;
|
|
60
60
|
/**
|
|
61
|
-
* @deprecated Use {@
|
|
61
|
+
* @deprecated Use {@link createBuilder} with `tx.run(zql.table.where(...))` instead.
|
|
62
62
|
*/
|
|
63
63
|
readonly query: ConditionalSchemaQuery<TSchema>;
|
|
64
64
|
constructor(lc: LogContext, repTx: WriteTransaction, schema: TSchema);
|
|
@@ -12,7 +12,7 @@ var TransactionImpl = class {
|
|
|
12
12
|
location = "client";
|
|
13
13
|
mutate;
|
|
14
14
|
/**
|
|
15
|
-
* @deprecated Use {@
|
|
15
|
+
* @deprecated Use {@link createBuilder} with `tx.run(zql.table.where(...))` instead.
|
|
16
16
|
*/
|
|
17
17
|
query;
|
|
18
18
|
#repTx;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom.js","names":["#repTx","#zeroContext"],"sources":["../../../../../zero-client/src/client/custom.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {ZeroTxData} from '../../../replicache/src/replicache-options.ts';\nimport type {WriteTransactionImpl} from '../../../replicache/src/transactions.ts';\nimport {zeroData} from '../../../replicache/src/transactions.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {emptyFunction} from '../../../shared/src/sentinels.ts';\nimport type {DefaultSchema} from '../../../zero-types/src/default-types.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {\n makeTransactionMutate,\n type TransactionMutate,\n} from '../../../zql/src/mutate/crud.ts';\nimport type {\n ClientTransaction,\n Transaction,\n} from '../../../zql/src/mutate/custom.ts';\nimport {createRunnableBuilder} from '../../../zql/src/query/create-builder.ts';\nimport {\n type HumanReadable,\n type Query,\n type RunOptions,\n} from '../../../zql/src/query/query.ts';\nimport type {ConditionalSchemaQuery} from '../../../zql/src/query/schema-query.ts';\nimport type {ClientID} from '../types/client-state.ts';\nimport {ZeroContext} from './context.ts';\nimport {makeCRUDExecutor} from './crud.ts';\nimport type {IVMSourceBranch} from './ivm-branch.ts';\nimport type {WriteTransaction} from './replicache-types.ts';\n\n/**\n * The shape which a user's custom mutator definitions must conform to.\n * Supports arbitrary depth nesting of namespaces.\n */\nexport type CustomMutatorDefs = {\n // oxlint-disable-next-line no-explicit-any\n [namespaceOrKey: string]: CustomMutatorImpl<any> | CustomMutatorDefs;\n};\n\nexport type MutatorResultDetails =\n | {\n readonly type: 'success';\n readonly data?: ReadonlyJSONValue | undefined;\n }\n | {\n readonly type: 'error';\n readonly error:\n | {\n readonly type: 'app';\n readonly message: string;\n readonly details: ReadonlyJSONValue | undefined;\n }\n | {\n readonly type: 'zero';\n readonly message: string;\n };\n };\n\nexport type MutatorResultSuccessDetails = Extract<\n MutatorResultDetails,\n {type: 'success'}\n>;\nexport type MutatorResultErrorDetails = Extract<\n MutatorResultDetails,\n {type: 'error'}\n>;\n\nexport type MutatorResult = {\n client: Promise<MutatorResultDetails & {}>;\n server: Promise<MutatorResultDetails & {}>;\n} & {};\n\nexport type CustomMutatorImpl<\n S extends Schema,\n TWrappedTransaction = unknown,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n TArgs = any,\n Context = unknown,\n> = (\n tx: Transaction<S, TWrappedTransaction>,\n // TODO: many args. See commit: 52657c2f934b4a458d628ea77e56ce92b61eb3c6 which did have many args.\n // The issue being that it will be a protocol change to support varargs.\n args: TArgs,\n ctx: Context,\n) => Promise<ReadonlyJSONValue | void>;\n\n/**\n * The shape exposed on the `Zero.mutate` instance.\n * The signature of a custom mutator takes a `transaction` as its first arg\n * but the user does not provide this arg when calling the mutator.\n *\n * This utility strips the `tx` arg from the user's custom mutator signatures.\n * Supports arbitrary depth nesting of namespaces.\n */\nexport type MakeCustomMutatorInterfaces<\n S extends Schema,\n MD extends CustomMutatorDefs,\n TContext,\n> = {\n readonly [NamespaceOrName in keyof MD]: MD[NamespaceOrName] extends (\n tx: Transaction<S>,\n ...args: infer Args\n ) => Promise<unknown>\n ? (...args: Args) => MutatorResult\n : MD[NamespaceOrName] extends CustomMutatorDefs\n ? MakeCustomMutatorInterfaces<S, MD[NamespaceOrName], TContext>\n : never;\n};\n\nexport type MakeCustomMutatorInterface<TSchema extends Schema, F> = F extends (\n tx: ClientTransaction<TSchema>,\n ...args: infer Args\n) => Promise<unknown>\n ? (...args: Args) => MutatorResult\n : never;\n\nexport class TransactionImpl<\n TSchema extends Schema = DefaultSchema,\n> implements ClientTransaction<TSchema> {\n readonly location = 'client';\n readonly mutate: TransactionMutate<TSchema>;\n /**\n * @deprecated Use {@
|
|
1
|
+
{"version":3,"file":"custom.js","names":["#repTx","#zeroContext"],"sources":["../../../../../zero-client/src/client/custom.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {ZeroTxData} from '../../../replicache/src/replicache-options.ts';\nimport type {WriteTransactionImpl} from '../../../replicache/src/transactions.ts';\nimport {zeroData} from '../../../replicache/src/transactions.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {emptyFunction} from '../../../shared/src/sentinels.ts';\nimport type {DefaultSchema} from '../../../zero-types/src/default-types.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {\n makeTransactionMutate,\n type TransactionMutate,\n} from '../../../zql/src/mutate/crud.ts';\nimport type {\n ClientTransaction,\n Transaction,\n} from '../../../zql/src/mutate/custom.ts';\nimport {createRunnableBuilder} from '../../../zql/src/query/create-builder.ts';\nimport {\n type HumanReadable,\n type Query,\n type RunOptions,\n} from '../../../zql/src/query/query.ts';\nimport type {ConditionalSchemaQuery} from '../../../zql/src/query/schema-query.ts';\nimport type {ClientID} from '../types/client-state.ts';\nimport {ZeroContext} from './context.ts';\nimport {makeCRUDExecutor} from './crud.ts';\nimport type {IVMSourceBranch} from './ivm-branch.ts';\nimport type {WriteTransaction} from './replicache-types.ts';\n\n/**\n * The shape which a user's custom mutator definitions must conform to.\n * Supports arbitrary depth nesting of namespaces.\n */\nexport type CustomMutatorDefs = {\n // oxlint-disable-next-line no-explicit-any\n [namespaceOrKey: string]: CustomMutatorImpl<any> | CustomMutatorDefs;\n};\n\nexport type MutatorResultDetails =\n | {\n readonly type: 'success';\n readonly data?: ReadonlyJSONValue | undefined;\n }\n | {\n readonly type: 'error';\n readonly error:\n | {\n readonly type: 'app';\n readonly message: string;\n readonly details: ReadonlyJSONValue | undefined;\n }\n | {\n readonly type: 'zero';\n readonly message: string;\n };\n };\n\nexport type MutatorResultSuccessDetails = Extract<\n MutatorResultDetails,\n {type: 'success'}\n>;\nexport type MutatorResultErrorDetails = Extract<\n MutatorResultDetails,\n {type: 'error'}\n>;\n\nexport type MutatorResult = {\n client: Promise<MutatorResultDetails & {}>;\n server: Promise<MutatorResultDetails & {}>;\n} & {};\n\nexport type CustomMutatorImpl<\n S extends Schema,\n TWrappedTransaction = unknown,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n TArgs = any,\n Context = unknown,\n> = (\n tx: Transaction<S, TWrappedTransaction>,\n // TODO: many args. See commit: 52657c2f934b4a458d628ea77e56ce92b61eb3c6 which did have many args.\n // The issue being that it will be a protocol change to support varargs.\n args: TArgs,\n ctx: Context,\n) => Promise<ReadonlyJSONValue | void>;\n\n/**\n * The shape exposed on the `Zero.mutate` instance.\n * The signature of a custom mutator takes a `transaction` as its first arg\n * but the user does not provide this arg when calling the mutator.\n *\n * This utility strips the `tx` arg from the user's custom mutator signatures.\n * Supports arbitrary depth nesting of namespaces.\n */\nexport type MakeCustomMutatorInterfaces<\n S extends Schema,\n MD extends CustomMutatorDefs,\n TContext,\n> = {\n readonly [NamespaceOrName in keyof MD]: MD[NamespaceOrName] extends (\n tx: Transaction<S>,\n ...args: infer Args\n ) => Promise<unknown>\n ? (...args: Args) => MutatorResult\n : MD[NamespaceOrName] extends CustomMutatorDefs\n ? MakeCustomMutatorInterfaces<S, MD[NamespaceOrName], TContext>\n : never;\n};\n\nexport type MakeCustomMutatorInterface<TSchema extends Schema, F> = F extends (\n tx: ClientTransaction<TSchema>,\n ...args: infer Args\n) => Promise<unknown>\n ? (...args: Args) => MutatorResult\n : never;\n\nexport class TransactionImpl<\n TSchema extends Schema = DefaultSchema,\n> implements ClientTransaction<TSchema> {\n readonly location = 'client';\n readonly mutate: TransactionMutate<TSchema>;\n /**\n * @deprecated Use {@link createBuilder} with `tx.run(zql.table.where(...))` instead.\n */\n readonly query: ConditionalSchemaQuery<TSchema>;\n\n readonly #repTx: WriteTransaction;\n readonly #zeroContext: ZeroContext;\n\n constructor(lc: LogContext, repTx: WriteTransaction, schema: TSchema) {\n must(repTx.reason === 'initial' || repTx.reason === 'rebase');\n const txData = getZeroTxData(repTx);\n const ivmBranch = txData.ivmSources as IVMSourceBranch;\n\n this.#repTx = repTx;\n\n const executor = makeCRUDExecutor(repTx, schema, ivmBranch);\n this.mutate = makeTransactionMutate(schema, executor);\n\n const zeroContext = newZeroContext(\n lc,\n txData.ivmSources as IVMSourceBranch,\n );\n\n this.query = createRunnableBuilder(zeroContext, schema);\n\n this.#zeroContext = zeroContext;\n }\n\n get clientID(): ClientID {\n return this.#repTx.clientID;\n }\n\n get mutationID(): number {\n return this.#repTx.mutationID;\n }\n\n get reason(): 'optimistic' | 'rebase' {\n return this.#repTx.reason === 'initial' ? 'optimistic' : 'rebase';\n }\n\n get token(): string | undefined {\n return (this.#repTx as WriteTransactionImpl)[zeroData]?.token;\n }\n\n run<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TTable, TSchema, TReturn>,\n options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n return this.#zeroContext.run(query, options);\n }\n}\n\nexport function getZeroTxData(repTx: WriteTransaction): ZeroTxData {\n const txData = must(\n (repTx as WriteTransactionImpl)[zeroData],\n 'zero was not set on replicache internal options!',\n );\n return txData as ZeroTxData;\n}\n\nexport function makeReplicacheMutator<\n S extends Schema,\n TWrappedTransaction,\n Context,\n>(\n lc: LogContext,\n mutator: CustomMutatorImpl<S, TWrappedTransaction>,\n schema: S,\n context: Context,\n): (\n repTx: WriteTransaction,\n args: ReadonlyJSONValue,\n) => Promise<ReadonlyJSONValue | void> {\n return async (repTx: WriteTransaction, args: ReadonlyJSONValue) => {\n const tx = new TransactionImpl(lc, repTx, schema);\n return await mutator(tx, args, context);\n };\n}\n\nfunction assertValidRunOptions(options: RunOptions | undefined): void {\n // TODO(arv): We should enforce this with the type system too.\n assert(\n options?.type !== 'complete' && options?.type !== 'cached',\n 'Cannot wait for cached or complete results in custom mutations',\n );\n}\n\nfunction newZeroContext(lc: LogContext, ivmBranch: IVMSourceBranch) {\n // The forked branch is already populated, so there is nothing to defer.\n return new ZeroContext(\n lc,\n ivmBranch,\n () => emptyFunction,\n () => emptyFunction,\n emptyFunction,\n emptyFunction,\n emptyFunction,\n applyViewUpdates => applyViewUpdates(),\n emptyFunction,\n assertValidRunOptions,\n ).markPipelinesReady();\n}\n"],"mappings":";;;;;;;;;;AAqHA,IAAa,kBAAb,MAEwC;CACtC,WAAoB;CACpB;;;;CAIA;CAEA;CACA;CAEA,YAAY,IAAgB,OAAyB,QAAiB;EACpE,KAAK,MAAM,WAAW,aAAa,MAAM,WAAW,QAAQ;EAC5D,MAAM,SAAS,cAAc,KAAK;EAClC,MAAM,YAAY,OAAO;EAEzB,KAAKA,SAAS;EAEd,MAAM,WAAW,iBAAiB,OAAO,QAAQ,SAAS;EAC1D,KAAK,SAAS,sBAAsB,QAAQ,QAAQ;EAEpD,MAAM,cAAc,eAClB,IACA,OAAO,UACT;EAEA,KAAK,QAAQ,sBAAsB,aAAa,MAAM;EAEtD,KAAKC,eAAe;CACtB;CAEA,IAAI,WAAqB;EACvB,OAAO,KAAKD,OAAO;CACrB;CAEA,IAAI,aAAqB;EACvB,OAAO,KAAKA,OAAO;CACrB;CAEA,IAAI,SAAkC;EACpC,OAAO,KAAKA,OAAO,WAAW,YAAY,eAAe;CAC3D;CAEA,IAAI,QAA4B;EAC9B,OAAQ,KAAKA,OAAgC,WAAW;CAC1D;CAEA,IACE,OACA,SACiC;EACjC,OAAO,KAAKC,aAAa,IAAI,OAAO,OAAO;CAC7C;AACF;AAEA,SAAgB,cAAc,OAAqC;CAKjE,OAJe,KACZ,MAA+B,WAChC,kDAEK;AACT;AAEA,SAAgB,sBAKd,IACA,SACA,QACA,SAIqC;CACrC,OAAO,OAAO,OAAyB,SAA4B;EAEjE,OAAO,MAAM,QAAQ,IADN,gBAAgB,IAAI,OAAO,MACrB,GAAI,MAAM,OAAO;CACxC;AACF;AAEA,SAAS,sBAAsB,SAAuC;CAEpE,OACE,SAAS,SAAS,cAAc,SAAS,SAAS,UAClD,gEACF;AACF;AAEA,SAAS,eAAe,IAAgB,WAA4B;CAElE,OAAO,IAAI,YACT,IACA,iBACM,qBACA,eACN,eACA,eACA,gBACA,qBAAoB,iBAAiB,GACrC,eACA,qBACF,EAAE,mBAAmB;AACvB"}
|
|
@@ -7,7 +7,7 @@ import type { AnyMutatorRegistry } from '../../../zql/src/mutate/mutator-registr
|
|
|
7
7
|
import type { CustomMutatorDefs } from './custom.ts';
|
|
8
8
|
import { UpdateNeededReasonType } from './update-needed-reason-type.ts';
|
|
9
9
|
/**
|
|
10
|
-
* Configuration for {@
|
|
10
|
+
* Configuration for {@link Zero}.
|
|
11
11
|
*/
|
|
12
12
|
export type ZeroOptions<S extends BaseDefaultSchema = DefaultSchema, MD extends CustomMutatorDefs | undefined = undefined, C extends BaseDefaultContext = DefaultContext> = {
|
|
13
13
|
/**
|
|
@@ -23,7 +23,7 @@ export type ZeroOptions<S extends BaseDefaultSchema = DefaultSchema, MD extends
|
|
|
23
23
|
*/
|
|
24
24
|
cacheURL?: string | null | undefined;
|
|
25
25
|
/**
|
|
26
|
-
* @deprecated Use {@
|
|
26
|
+
* @deprecated Use {@link cacheURL} instead.
|
|
27
27
|
*/
|
|
28
28
|
server?: string | null | undefined;
|
|
29
29
|
/**
|
|
@@ -77,7 +77,7 @@ export type ZeroOptions<S extends BaseDefaultSchema = DefaultSchema, MD extends
|
|
|
77
77
|
* Destination for Zero's log output. When omitted, logs are written with
|
|
78
78
|
* `console.log`/`info`/`warn`/`error` etc.
|
|
79
79
|
*
|
|
80
|
-
* Provide a custom {@
|
|
80
|
+
* Provide a custom {@link LogSink} to redirect logs — for example, to
|
|
81
81
|
* `process.stderr` in a CLI so that the tool's structured stdout output
|
|
82
82
|
* isn't polluted by log lines.
|
|
83
83
|
*
|
|
@@ -145,7 +145,7 @@ export type ZeroOptions<S extends BaseDefaultSchema = DefaultSchema, MD extends
|
|
|
145
145
|
* Custom URL for query requests sent to your API server.
|
|
146
146
|
* If not provided, uses the default configured in zero-cache.
|
|
147
147
|
*
|
|
148
|
-
* @deprecated Use {@
|
|
148
|
+
* @deprecated Use {@link queryURL} instead.
|
|
149
149
|
*/
|
|
150
150
|
getQueriesURL?: string | undefined;
|
|
151
151
|
/**
|
|
@@ -184,7 +184,7 @@ export type ZeroOptions<S extends BaseDefaultSchema = DefaultSchema, MD extends
|
|
|
184
184
|
/**
|
|
185
185
|
* `onOnlineChange` is called when the Zero instance's online status changes.
|
|
186
186
|
*
|
|
187
|
-
* @deprecated Use {@
|
|
187
|
+
* @deprecated Use {@link Connection.state.subscribe} on the Zero instance instead. e.g.
|
|
188
188
|
* ```ts
|
|
189
189
|
* const zero = new Zero({...});
|
|
190
190
|
* zero.connection.state.subscribe((state) => {
|
|
@@ -192,7 +192,7 @@ export type ZeroOptions<S extends BaseDefaultSchema = DefaultSchema, MD extends
|
|
|
192
192
|
* });
|
|
193
193
|
* ```
|
|
194
194
|
*
|
|
195
|
-
* Or use a hook like {@
|
|
195
|
+
* Or use a hook like {@link useConnectionState} to subscribe to state changes.
|
|
196
196
|
*/
|
|
197
197
|
onOnlineChange?: ((online: boolean) => void) | undefined;
|
|
198
198
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","names":[],"sources":["../../../../../zero-client/src/client/options.ts"],"sourcesContent":["import type {LogLevel, LogSink} from '@rocicorp/logger';\nimport type {StoreProvider} from '../../../replicache/src/kv/store.ts';\nimport type {StorageFailureError} from '../../../replicache/src/storage-failure.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {\n BaseDefaultContext,\n BaseDefaultSchema,\n DefaultContext,\n DefaultSchema,\n} from '../../../zero-types/src/default-types.ts';\nimport type {AnyMutatorRegistry} from '../../../zql/src/mutate/mutator-registry.ts';\nimport type {CustomMutatorDefs} from './custom.ts';\nimport {UpdateNeededReasonType} from './update-needed-reason-type.ts';\n\n/**\n * Configuration for {@linkcode Zero}.\n */\nexport type ZeroOptions<\n S extends BaseDefaultSchema = DefaultSchema,\n MD extends CustomMutatorDefs | undefined = undefined,\n C extends BaseDefaultContext = DefaultContext,\n> = {\n /**\n * URL to the zero-cache. This can be a simple hostname, e.g.\n * - \"https://myapp-myteam.zero.ms\"\n * or a prefix with a single path component, e.g.\n * - \"https://myapp-myteam.zero.ms/zero\"\n * - \"https://myapp-myteam.zero.ms/db\"\n *\n * The latter is useful for configuring routing rules (e.g. \"/zero/\\*\") when\n * the zero-cache is hosted on the same domain as the application. **Note that\n * only a single path segment is allowed (e.g. it cannot be \"/proxy/zero/\\*\")**.\n */\n cacheURL?: string | null | undefined;\n\n /**\n * @deprecated Use {@linkcode cacheURL} instead.\n */\n server?: string | null | undefined;\n\n /**\n * A token to identify and authenticate the user.\n *\n * Set `auth` to `null` or `undefined` if there is no logged in user.\n *\n * The call to `connect` is handled automatically by the ZeroProvider component\n * for React and SolidJS when the `auth` prop changes.\n *\n * Transitions between authenticated and logged-out states recreate the Zero\n * instance instead.\n *\n * When `auth` changes while connected, Zero refreshes server-side auth context\n * and re-transforms queries without reconnecting.\n *\n * When a 401 or 403 HTTP status code is received from your server, Zero will\n * transition to the `needs-auth` connection state. The app should call\n * `zero.connection.connect({auth: newToken})` with a new token to reconnect.\n */\n auth?: string | null | undefined;\n\n /**\n * A unique identifier for the user.\n *\n * Omit this, or set to `null`, for logged-out clients.\n *\n * Each userID gets its own client-side storage so that the app can switch\n * between users without losing state.\n */\n userID?: string | null | undefined;\n\n /**\n * Distinguishes the storage used by this Zero instance from that of other\n * instances with the same userID. Useful in the case where the app wants to\n * have multiple Zero instances for the same user for different parts of the\n * app.\n */\n storageKey?: string | undefined;\n\n /**\n * Determines the level of detail at which Zero logs messages about\n * its operation. Messages are logged to the `console`.\n *\n * When this is set to `'debug'`, `'info'` and `'error'` messages are also\n * logged. When set to `'info'`, `'info'` and `'error'` but not\n * `'debug'` messages are logged. When set to `'error'` only `'error'`\n * messages are logged.\n *\n * Default is `'error'`.\n */\n logLevel?: LogLevel | undefined;\n\n /**\n * Destination for Zero's log output. When omitted, logs are written with\n * `console.log`/`info`/`warn`/`error` etc.\n *\n * Provide a custom {@linkcode LogSink} to redirect logs — for example, to\n * `process.stderr` in a CLI so that the tool's structured stdout output\n * isn't polluted by log lines.\n *\n * Note: this overrides only the console sink. When analytics logging is\n * enabled, the Datadog sink is still installed alongside this one.\n */\n logSink?: LogSink | undefined;\n\n /**\n * This defines the schema of the tables used in Zero and their relationships\n * to one another.\n */\n schema: S;\n\n /**\n * `mutators` is a map of custom mutator definitions. The keys are\n * namespaces or names of the mutators. The values are the mutator\n * implementations. Client side mutators must be idempotent as a\n * mutation can be rebased multiple times when folding in authoritative\n * changes from the server to the client.\n *\n * Define mutators using the `defineMutator` function to create type-safe,\n * parameterized mutations. Mutators can be top-level or grouped in namespaces.\n *\n * @example\n * ```ts\n * import {defineMutator} from '@rocicorp/zero';\n *\n * const z = new Zero({\n * schema,\n * userID,\n * mutators: {\n * // Top-level mutator\n * increment: defineMutator(({tx, args}: {tx: Transaction<Schema>, args: {id: string}}) =>\n * tx.mutate.counter.update({id: args.id, value: tx.query.counter.where('id', '=', args.id).value + 1})\n * ),\n * // Namespace with multiple mutators\n * issues: {\n * create: defineMutator(({tx, args}: {tx: Transaction<Schema>, args: {title: string}}) =>\n * tx.mutate.issues.insert({id: nanoid(), title: args.title, status: 'open'})\n * ),\n * close: defineMutator(({tx, args}: {tx: Transaction<Schema>, args: {id: string}}) =>\n * tx.mutate.issues.update({id: args.id, status: 'closed'})\n * ),\n * },\n * },\n * });\n *\n * // Usage\n * await z.mutate.increment({id: 'counter-1'}).client;\n * await z.mutate.issues.create({title: 'New issue'}).client;\n * await z.mutate.issues.close({id: 'issue-123'}).client;\n * ```\n */\n mutators?: MD extends CustomMutatorDefs ? MD : AnyMutatorRegistry | undefined;\n\n /**\n * Custom URL for mutation requests sent to your API server.\n * If not provided, uses the default configured in zero-cache.\n */\n mutateURL?: string | undefined;\n\n /**\n * Custom headers to include in mutation requests sent to your API server.\n * These headers are passed through zero-cache to the mutate endpoint.\n */\n mutateHeaders?: Record<string, string> | undefined;\n\n /**\n * Custom URL for query requests sent to your API server.\n * If not provided, uses the default configured in zero-cache.\n *\n * @deprecated Use {@linkcode queryURL} instead.\n */\n getQueriesURL?: string | undefined;\n\n /**\n * Custom URL for query requests sent to your API server.\n * If not provided, uses the default configured in zero-cache.\n */\n queryURL?: string | undefined;\n\n /**\n * Custom headers to include in query requests sent to your API server.\n * These headers are passed through zero-cache to the query endpoint.\n */\n queryHeaders?: Record<string, string> | undefined;\n\n /**\n * Optional callback that returns a W3C `traceparent` header value for\n * distributed tracing. Called before sending WebSocket messages that\n * trigger API server calls (`push`, `changeDesiredQueries`,\n * `initConnection`).\n *\n * This enables end-to-end trace correlation from your frontend through\n * zero-cache to your API server.\n *\n * @example\n * ```ts\n * import {propagation, context} from '@opentelemetry/api';\n *\n * new Zero({\n * getTraceparent: () => {\n * const carrier: Record<string, string> = {};\n * propagation.inject(context.active(), carrier);\n * return carrier.traceparent;\n * },\n * });\n * ```\n */\n getTraceparent?: (() => string | undefined) | undefined;\n\n /**\n * `onOnlineChange` is called when the Zero instance's online status changes.\n *\n * @deprecated Use {@linkcode Connection.state.subscribe} on the Zero instance instead. e.g.\n * ```ts\n * const zero = new Zero({...});\n * zero.connection.state.subscribe((state) => {\n * console.log('Connection state:', state.name);\n * });\n * ```\n *\n * Or use a hook like {@linkcode useConnectionState} to subscribe to state changes.\n */\n onOnlineChange?: ((online: boolean) => void) | undefined;\n\n /**\n * `onUpdateNeeded` is called when a client code update is needed.\n *\n * See {@link UpdateNeededReason} for why updates can be needed.\n *\n * The default behavior is to reload the page (using `location.reload()`).\n * Provide your own function to prevent the page from\n * reloading automatically. You may want to display a toast to inform the end\n * user there is a new version of your app available and prompt them to\n * refresh.\n */\n onUpdateNeeded?: ((reason: UpdateNeededReason) => void) | undefined;\n\n /**\n * `onClientStateNotFound` is called when this client is no longer able\n * to sync with zero-cache due to missing synchronization state. This\n * can be because:\n * - the local persistent synchronization state has been garbage collected.\n * This can happen if the client has no pending mutations and has not been\n * used for a while (e.g. the client's tab has been hidden for a long time).\n * - the local persistent synchronization state was found to be corrupt.\n * Replicache tries to drop the local database so that a reload starts\n * fresh. The callback is called even if that fails.\n * - zero-cache fails to find the server side synchronization state for\n * this client.\n * - zero-cache rejects this client's persisted synchronization state,\n * requiring the local Replicache database to be reset.\n *\n * The current `Zero` instance should be treated as dead and replaced, not\n * reconnected.\n *\n * The default behavior for the Zero client is to reload the page\n * (using `location.reload()`). The React and SolidJS providers will replace\n * the client in-place, without a full page reload.\n *\n * Provide your own function to prevent this functionality.\n */\n onClientStateNotFound?: (() => void) | undefined;\n\n /**\n * `onStorageFailure` is called once, the first time the local persistent\n * store reports that its storage has failed rather than its data, with the\n * {@link StorageFailureError} it threw: the device is out of space\n * (`full`), the database cannot be opened (`cannot-open`, including an\n * IndexedDB that fails to open), or a read or write to it failed\n * (`io-error`).\n *\n * A failure while this `Zero` instance is opening runs it on memory for the\n * session, as with `kvStore: 'mem'`: nothing is written to disk. A failure\n * after that stops persisting locally: queries and mutations keep running\n * against what the in-memory dag holds, and mutations keep pushing to\n * zero-cache, but nothing is written to disk until a new instance is\n * created; a read that needs a chunk not yet loaded\n * from the store (or since evicted from the in-memory cache) still fails\n * the way any store read does, and is reported here too, whichever caller\n * made it. An invalid ref count seen while the store is\n * failing with `cannot-open` or `io-error` is not treated as corruption, so\n * {@link onClientStateNotFound} is not called for it: a store that cannot\n * complete its reads and writes produces such readings without the data\n * being corrupt, and dropping the database would rebuild onto the same\n * failing storage. A `full` disk keeps the drop, since deleting the database\n * is also what frees the space.\n *\n * The default behavior is to log the failure and nothing else. Provide a\n * function to show the user what to do — free up space, or restart the\n * app once the storage is available again.\n */\n onStorageFailure?: ((failure: StorageFailureError) => void) | undefined;\n\n /**\n * The number of milliseconds to wait before disconnecting a Zero\n * instance whose tab has become hidden.\n *\n * Instances in hidden tabs are disconnected to save resources.\n *\n * Default is 5 minutes.\n */\n hiddenTabDisconnectDelay?: number | undefined;\n\n /**\n * The number of milliseconds to wait before disconnecting a Zero\n * instance when the connection to the server has timed out.\n *\n * Default is 1 minute.\n */\n disconnectTimeoutMs?: number | undefined;\n\n /**\n * The timeout in milliseconds for ping operations. This value is used for:\n * - How long to wait in idle before sending a ping to the server\n * - How long to wait for a pong response after sending a ping\n *\n * Total time to detect a dead connection is 2 × pingTimeoutMs.\n *\n * Default is 5_000.\n */\n pingTimeoutMs?: number | undefined;\n\n /**\n * Determines what kind of storage implementation to use on the client.\n *\n * Defaults to `'idb'` which means that Zero uses an IndexedDB storage\n * implementation. This allows the data to be persisted on the client and\n * enables faster syncs between application restarts.\n *\n * By setting this to `'mem'`, Zero uses an in memory storage and\n * the data is not persisted on the client.\n *\n * You can also set this to a function that is used to create new KV stores,\n * allowing a custom implementation of the underlying storage layer.\n */\n kvStore?: 'mem' | 'idb' | StoreProvider | undefined;\n\n /**\n * The maximum number of bytes to allow in a single header.\n *\n * Zero adds some extra information to headers on initialization if possible.\n * This speeds up data synchronization. This number should be kept less than\n * or equal to the maximum header size allowed by the zero-cache and any load\n * balancers.\n *\n * Default value: 8kb.\n */\n maxHeaderLength?: number | undefined;\n\n /**\n * The maximum amount of milliseconds to wait for a materialization to\n * complete (including network/server time) before printing a warning to the\n * console.\n *\n * Default value: 5_000.\n */\n slowMaterializeThreshold?: number | undefined;\n\n /**\n * UI rendering libraries will often provide a utility for batching multiple\n * state updates into a single render. Some examples are React's\n * `unstable_batchedUpdates`, and solid-js's `batch`.\n *\n * This option enables integrating these batch utilities with Zero.\n *\n * When `batchViewUpdates` is provided, Zero will call it whenever\n * it updates query view state with an `applyViewUpdates` function\n * that performs the actual state updates.\n *\n * Zero updates query view state when:\n * 1. creating a new view\n * 2. updating all existing queries' views to a new consistent state\n *\n * When creating a new view, that single view's creation will be wrapped\n * in a `batchViewUpdates` call.\n *\n * When updating existing queries, all queries will be updated in a single\n * `batchViewUpdates` call, so that the transition to the new consistent\n * state can be done in a single render.\n *\n * Implementations must always call `applyViewUpdates` synchronously.\n */\n batchViewUpdates?: ((applyViewUpdates: () => void) => void) | undefined;\n\n /**\n * The maximum number of recent queries, no longer subscribed to by a preload\n * or view, to continue syncing.\n *\n * Defaults is 0.\n *\n * @deprecated Use ttl instead\n */\n maxRecentQueries?: number | undefined;\n\n /**\n * Changes to queries are sent to server in batches. This option controls\n * the number of milliseconds to wait before sending the next batch.\n *\n * Defaults is 10.\n */\n queryChangeThrottleMs?: number | undefined;\n\n /**\n * Context is passed to queries when they are executed.\n */\n context?: C | undefined;\n} & (unknown extends DefaultContext\n ? {}\n : {\n context: C;\n });\n\n/**\n * @deprecated Use {@link ZeroOptions} instead.\n */\nexport type ZeroAdvancedOptions<\n S extends BaseDefaultSchema,\n MD extends CustomMutatorDefs | undefined,\n Context extends BaseDefaultContext,\n> = ZeroOptions<S, MD, Context>;\n\ntype UpdateNeededReasonBase = {\n message?: string;\n};\n\nexport type UpdateNeededReason =\n | ({type: UpdateNeededReasonType.NewClientGroup} & UpdateNeededReasonBase)\n | ({\n type: UpdateNeededReasonType.VersionNotSupported;\n } & UpdateNeededReasonBase)\n | ({\n type: UpdateNeededReasonType.SchemaVersionNotSupported;\n } & UpdateNeededReasonBase);\n\nexport const updateNeededReasonTypeSchema: v.Type<UpdateNeededReason['type']> =\n v.literalUnion(\n UpdateNeededReasonType.NewClientGroup,\n UpdateNeededReasonType.VersionNotSupported,\n UpdateNeededReasonType.SchemaVersionNotSupported,\n );\n"],"mappings":";;;AAibA,IAAa,+BACX,aACE,gBACA,qBACA,yBACF"}
|
|
1
|
+
{"version":3,"file":"options.js","names":[],"sources":["../../../../../zero-client/src/client/options.ts"],"sourcesContent":["import type {LogLevel, LogSink} from '@rocicorp/logger';\nimport type {StoreProvider} from '../../../replicache/src/kv/store.ts';\nimport type {StorageFailureError} from '../../../replicache/src/storage-failure.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {\n BaseDefaultContext,\n BaseDefaultSchema,\n DefaultContext,\n DefaultSchema,\n} from '../../../zero-types/src/default-types.ts';\nimport type {AnyMutatorRegistry} from '../../../zql/src/mutate/mutator-registry.ts';\nimport type {CustomMutatorDefs} from './custom.ts';\nimport {UpdateNeededReasonType} from './update-needed-reason-type.ts';\n\n/**\n * Configuration for {@link Zero}.\n */\nexport type ZeroOptions<\n S extends BaseDefaultSchema = DefaultSchema,\n MD extends CustomMutatorDefs | undefined = undefined,\n C extends BaseDefaultContext = DefaultContext,\n> = {\n /**\n * URL to the zero-cache. This can be a simple hostname, e.g.\n * - \"https://myapp-myteam.zero.ms\"\n * or a prefix with a single path component, e.g.\n * - \"https://myapp-myteam.zero.ms/zero\"\n * - \"https://myapp-myteam.zero.ms/db\"\n *\n * The latter is useful for configuring routing rules (e.g. \"/zero/\\*\") when\n * the zero-cache is hosted on the same domain as the application. **Note that\n * only a single path segment is allowed (e.g. it cannot be \"/proxy/zero/\\*\")**.\n */\n cacheURL?: string | null | undefined;\n\n /**\n * @deprecated Use {@link cacheURL} instead.\n */\n server?: string | null | undefined;\n\n /**\n * A token to identify and authenticate the user.\n *\n * Set `auth` to `null` or `undefined` if there is no logged in user.\n *\n * The call to `connect` is handled automatically by the ZeroProvider component\n * for React and SolidJS when the `auth` prop changes.\n *\n * Transitions between authenticated and logged-out states recreate the Zero\n * instance instead.\n *\n * When `auth` changes while connected, Zero refreshes server-side auth context\n * and re-transforms queries without reconnecting.\n *\n * When a 401 or 403 HTTP status code is received from your server, Zero will\n * transition to the `needs-auth` connection state. The app should call\n * `zero.connection.connect({auth: newToken})` with a new token to reconnect.\n */\n auth?: string | null | undefined;\n\n /**\n * A unique identifier for the user.\n *\n * Omit this, or set to `null`, for logged-out clients.\n *\n * Each userID gets its own client-side storage so that the app can switch\n * between users without losing state.\n */\n userID?: string | null | undefined;\n\n /**\n * Distinguishes the storage used by this Zero instance from that of other\n * instances with the same userID. Useful in the case where the app wants to\n * have multiple Zero instances for the same user for different parts of the\n * app.\n */\n storageKey?: string | undefined;\n\n /**\n * Determines the level of detail at which Zero logs messages about\n * its operation. Messages are logged to the `console`.\n *\n * When this is set to `'debug'`, `'info'` and `'error'` messages are also\n * logged. When set to `'info'`, `'info'` and `'error'` but not\n * `'debug'` messages are logged. When set to `'error'` only `'error'`\n * messages are logged.\n *\n * Default is `'error'`.\n */\n logLevel?: LogLevel | undefined;\n\n /**\n * Destination for Zero's log output. When omitted, logs are written with\n * `console.log`/`info`/`warn`/`error` etc.\n *\n * Provide a custom {@link LogSink} to redirect logs — for example, to\n * `process.stderr` in a CLI so that the tool's structured stdout output\n * isn't polluted by log lines.\n *\n * Note: this overrides only the console sink. When analytics logging is\n * enabled, the Datadog sink is still installed alongside this one.\n */\n logSink?: LogSink | undefined;\n\n /**\n * This defines the schema of the tables used in Zero and their relationships\n * to one another.\n */\n schema: S;\n\n /**\n * `mutators` is a map of custom mutator definitions. The keys are\n * namespaces or names of the mutators. The values are the mutator\n * implementations. Client side mutators must be idempotent as a\n * mutation can be rebased multiple times when folding in authoritative\n * changes from the server to the client.\n *\n * Define mutators using the `defineMutator` function to create type-safe,\n * parameterized mutations. Mutators can be top-level or grouped in namespaces.\n *\n * @example\n * ```ts\n * import {defineMutator} from '@rocicorp/zero';\n *\n * const z = new Zero({\n * schema,\n * userID,\n * mutators: {\n * // Top-level mutator\n * increment: defineMutator(({tx, args}: {tx: Transaction<Schema>, args: {id: string}}) =>\n * tx.mutate.counter.update({id: args.id, value: tx.query.counter.where('id', '=', args.id).value + 1})\n * ),\n * // Namespace with multiple mutators\n * issues: {\n * create: defineMutator(({tx, args}: {tx: Transaction<Schema>, args: {title: string}}) =>\n * tx.mutate.issues.insert({id: nanoid(), title: args.title, status: 'open'})\n * ),\n * close: defineMutator(({tx, args}: {tx: Transaction<Schema>, args: {id: string}}) =>\n * tx.mutate.issues.update({id: args.id, status: 'closed'})\n * ),\n * },\n * },\n * });\n *\n * // Usage\n * await z.mutate.increment({id: 'counter-1'}).client;\n * await z.mutate.issues.create({title: 'New issue'}).client;\n * await z.mutate.issues.close({id: 'issue-123'}).client;\n * ```\n */\n mutators?: MD extends CustomMutatorDefs ? MD : AnyMutatorRegistry | undefined;\n\n /**\n * Custom URL for mutation requests sent to your API server.\n * If not provided, uses the default configured in zero-cache.\n */\n mutateURL?: string | undefined;\n\n /**\n * Custom headers to include in mutation requests sent to your API server.\n * These headers are passed through zero-cache to the mutate endpoint.\n */\n mutateHeaders?: Record<string, string> | undefined;\n\n /**\n * Custom URL for query requests sent to your API server.\n * If not provided, uses the default configured in zero-cache.\n *\n * @deprecated Use {@link queryURL} instead.\n */\n getQueriesURL?: string | undefined;\n\n /**\n * Custom URL for query requests sent to your API server.\n * If not provided, uses the default configured in zero-cache.\n */\n queryURL?: string | undefined;\n\n /**\n * Custom headers to include in query requests sent to your API server.\n * These headers are passed through zero-cache to the query endpoint.\n */\n queryHeaders?: Record<string, string> | undefined;\n\n /**\n * Optional callback that returns a W3C `traceparent` header value for\n * distributed tracing. Called before sending WebSocket messages that\n * trigger API server calls (`push`, `changeDesiredQueries`,\n * `initConnection`).\n *\n * This enables end-to-end trace correlation from your frontend through\n * zero-cache to your API server.\n *\n * @example\n * ```ts\n * import {propagation, context} from '@opentelemetry/api';\n *\n * new Zero({\n * getTraceparent: () => {\n * const carrier: Record<string, string> = {};\n * propagation.inject(context.active(), carrier);\n * return carrier.traceparent;\n * },\n * });\n * ```\n */\n getTraceparent?: (() => string | undefined) | undefined;\n\n /**\n * `onOnlineChange` is called when the Zero instance's online status changes.\n *\n * @deprecated Use {@link Connection.state.subscribe} on the Zero instance instead. e.g.\n * ```ts\n * const zero = new Zero({...});\n * zero.connection.state.subscribe((state) => {\n * console.log('Connection state:', state.name);\n * });\n * ```\n *\n * Or use a hook like {@link useConnectionState} to subscribe to state changes.\n */\n onOnlineChange?: ((online: boolean) => void) | undefined;\n\n /**\n * `onUpdateNeeded` is called when a client code update is needed.\n *\n * See {@link UpdateNeededReason} for why updates can be needed.\n *\n * The default behavior is to reload the page (using `location.reload()`).\n * Provide your own function to prevent the page from\n * reloading automatically. You may want to display a toast to inform the end\n * user there is a new version of your app available and prompt them to\n * refresh.\n */\n onUpdateNeeded?: ((reason: UpdateNeededReason) => void) | undefined;\n\n /**\n * `onClientStateNotFound` is called when this client is no longer able\n * to sync with zero-cache due to missing synchronization state. This\n * can be because:\n * - the local persistent synchronization state has been garbage collected.\n * This can happen if the client has no pending mutations and has not been\n * used for a while (e.g. the client's tab has been hidden for a long time).\n * - the local persistent synchronization state was found to be corrupt.\n * Replicache tries to drop the local database so that a reload starts\n * fresh. The callback is called even if that fails.\n * - zero-cache fails to find the server side synchronization state for\n * this client.\n * - zero-cache rejects this client's persisted synchronization state,\n * requiring the local Replicache database to be reset.\n *\n * The current `Zero` instance should be treated as dead and replaced, not\n * reconnected.\n *\n * The default behavior for the Zero client is to reload the page\n * (using `location.reload()`). The React and SolidJS providers will replace\n * the client in-place, without a full page reload.\n *\n * Provide your own function to prevent this functionality.\n */\n onClientStateNotFound?: (() => void) | undefined;\n\n /**\n * `onStorageFailure` is called once, the first time the local persistent\n * store reports that its storage has failed rather than its data, with the\n * {@link StorageFailureError} it threw: the device is out of space\n * (`full`), the database cannot be opened (`cannot-open`, including an\n * IndexedDB that fails to open), or a read or write to it failed\n * (`io-error`).\n *\n * A failure while this `Zero` instance is opening runs it on memory for the\n * session, as with `kvStore: 'mem'`: nothing is written to disk. A failure\n * after that stops persisting locally: queries and mutations keep running\n * against what the in-memory dag holds, and mutations keep pushing to\n * zero-cache, but nothing is written to disk until a new instance is\n * created; a read that needs a chunk not yet loaded\n * from the store (or since evicted from the in-memory cache) still fails\n * the way any store read does, and is reported here too, whichever caller\n * made it. An invalid ref count seen while the store is\n * failing with `cannot-open` or `io-error` is not treated as corruption, so\n * {@link onClientStateNotFound} is not called for it: a store that cannot\n * complete its reads and writes produces such readings without the data\n * being corrupt, and dropping the database would rebuild onto the same\n * failing storage. A `full` disk keeps the drop, since deleting the database\n * is also what frees the space.\n *\n * The default behavior is to log the failure and nothing else. Provide a\n * function to show the user what to do — free up space, or restart the\n * app once the storage is available again.\n */\n onStorageFailure?: ((failure: StorageFailureError) => void) | undefined;\n\n /**\n * The number of milliseconds to wait before disconnecting a Zero\n * instance whose tab has become hidden.\n *\n * Instances in hidden tabs are disconnected to save resources.\n *\n * Default is 5 minutes.\n */\n hiddenTabDisconnectDelay?: number | undefined;\n\n /**\n * The number of milliseconds to wait before disconnecting a Zero\n * instance when the connection to the server has timed out.\n *\n * Default is 1 minute.\n */\n disconnectTimeoutMs?: number | undefined;\n\n /**\n * The timeout in milliseconds for ping operations. This value is used for:\n * - How long to wait in idle before sending a ping to the server\n * - How long to wait for a pong response after sending a ping\n *\n * Total time to detect a dead connection is 2 × pingTimeoutMs.\n *\n * Default is 5_000.\n */\n pingTimeoutMs?: number | undefined;\n\n /**\n * Determines what kind of storage implementation to use on the client.\n *\n * Defaults to `'idb'` which means that Zero uses an IndexedDB storage\n * implementation. This allows the data to be persisted on the client and\n * enables faster syncs between application restarts.\n *\n * By setting this to `'mem'`, Zero uses an in memory storage and\n * the data is not persisted on the client.\n *\n * You can also set this to a function that is used to create new KV stores,\n * allowing a custom implementation of the underlying storage layer.\n */\n kvStore?: 'mem' | 'idb' | StoreProvider | undefined;\n\n /**\n * The maximum number of bytes to allow in a single header.\n *\n * Zero adds some extra information to headers on initialization if possible.\n * This speeds up data synchronization. This number should be kept less than\n * or equal to the maximum header size allowed by the zero-cache and any load\n * balancers.\n *\n * Default value: 8kb.\n */\n maxHeaderLength?: number | undefined;\n\n /**\n * The maximum amount of milliseconds to wait for a materialization to\n * complete (including network/server time) before printing a warning to the\n * console.\n *\n * Default value: 5_000.\n */\n slowMaterializeThreshold?: number | undefined;\n\n /**\n * UI rendering libraries will often provide a utility for batching multiple\n * state updates into a single render. Some examples are React's\n * `unstable_batchedUpdates`, and solid-js's `batch`.\n *\n * This option enables integrating these batch utilities with Zero.\n *\n * When `batchViewUpdates` is provided, Zero will call it whenever\n * it updates query view state with an `applyViewUpdates` function\n * that performs the actual state updates.\n *\n * Zero updates query view state when:\n * 1. creating a new view\n * 2. updating all existing queries' views to a new consistent state\n *\n * When creating a new view, that single view's creation will be wrapped\n * in a `batchViewUpdates` call.\n *\n * When updating existing queries, all queries will be updated in a single\n * `batchViewUpdates` call, so that the transition to the new consistent\n * state can be done in a single render.\n *\n * Implementations must always call `applyViewUpdates` synchronously.\n */\n batchViewUpdates?: ((applyViewUpdates: () => void) => void) | undefined;\n\n /**\n * The maximum number of recent queries, no longer subscribed to by a preload\n * or view, to continue syncing.\n *\n * Defaults is 0.\n *\n * @deprecated Use ttl instead\n */\n maxRecentQueries?: number | undefined;\n\n /**\n * Changes to queries are sent to server in batches. This option controls\n * the number of milliseconds to wait before sending the next batch.\n *\n * Defaults is 10.\n */\n queryChangeThrottleMs?: number | undefined;\n\n /**\n * Context is passed to queries when they are executed.\n */\n context?: C | undefined;\n} & (unknown extends DefaultContext\n ? {}\n : {\n context: C;\n });\n\n/**\n * @deprecated Use {@link ZeroOptions} instead.\n */\nexport type ZeroAdvancedOptions<\n S extends BaseDefaultSchema,\n MD extends CustomMutatorDefs | undefined,\n Context extends BaseDefaultContext,\n> = ZeroOptions<S, MD, Context>;\n\ntype UpdateNeededReasonBase = {\n message?: string;\n};\n\nexport type UpdateNeededReason =\n | ({type: UpdateNeededReasonType.NewClientGroup} & UpdateNeededReasonBase)\n | ({\n type: UpdateNeededReasonType.VersionNotSupported;\n } & UpdateNeededReasonBase)\n | ({\n type: UpdateNeededReasonType.SchemaVersionNotSupported;\n } & UpdateNeededReasonBase);\n\nexport const updateNeededReasonTypeSchema: v.Type<UpdateNeededReason['type']> =\n v.literalUnion(\n UpdateNeededReasonType.NewClientGroup,\n UpdateNeededReasonType.VersionNotSupported,\n UpdateNeededReasonType.SchemaVersionNotSupported,\n );\n"],"mappings":";;;AAibA,IAAa,+BACX,aACE,gBACA,qBACA,yBACF"}
|
|
@@ -95,7 +95,7 @@ export declare class Zero<const S extends BaseDefaultSchema = DefaultSchema, MD
|
|
|
95
95
|
/**
|
|
96
96
|
* Query builders for each table in the schema.
|
|
97
97
|
*
|
|
98
|
-
* @deprecated Use {@
|
|
98
|
+
* @deprecated Use {@link createBuilder} to create query builders instead.
|
|
99
99
|
*/
|
|
100
100
|
readonly query: ConditionalSchemaQuery<S>;
|
|
101
101
|
/**
|
|
@@ -221,7 +221,7 @@ var Zero = class Zero {
|
|
|
221
221
|
/**
|
|
222
222
|
* Query builders for each table in the schema.
|
|
223
223
|
*
|
|
224
|
-
* @deprecated Use {@
|
|
224
|
+
* @deprecated Use {@link createBuilder} to create query builders instead.
|
|
225
225
|
*/
|
|
226
226
|
query;
|
|
227
227
|
#metrics;
|