import type { NullToOptional } from './helpers/NullToOptional' import type { TupleToUnion } from './helpers/tuple' import type { Condition, ExpressionBuilder, Row, SchemaQuery, TableBuilderWithColumns, Schema as ZeroSchema, Transaction as ZeroTransaction, } from '@rocicorp/zero' /** * ➗0️⃣ START OVERRIDDEN TYPES * * To get types, put the following in a .ts file that's included by your tsconfig: * * export type Schema = typeof schema * * declare module 'on-zero' { * interface Config { * schema: Schema * authData: AuthData * } * } * * on-zero is overridden by consumers of this library to get types which is * needed to allow co-locating certain typed helpers like where() and * mutations() alongside table() because table is later used to create the Zero * schema, which is then needed for where/mutations */ export interface Config {} interface DefaultConfig { schema: ZeroSchema authData: {} serverActions: null asyncAction: never } interface FinalConfig extends Omit, Config {} export type Schema = FinalConfig['schema'] export type TableName = keyof Schema['tables'] extends string ? keyof Schema['tables'] : string // eslint-disable-next-line typescript-eslint/no-unnecessary-type-arguments export type Transaction = ZeroTransaction export type AuthData = FinalConfig['authData'] extends Record ? FinalConfig['authData'] : Record export type ServerActions = FinalConfig['serverActions'] extends Record ? FinalConfig['serverActions'] : Record export type AsyncActionEnvelope = { readonly type: string readonly [field: string]: unknown } export type AsyncAction = FinalConfig['asyncAction'] extends AsyncActionEnvelope ? FinalConfig['asyncAction'] : never export type QueryBuilder = SchemaQuery export type AsyncTask = () => Promise export type AsyncTaskOptions = { barrier?: boolean } /** * ➗0️⃣ END OVERRIDDEN TYPES */ // the first argument passed to every mutation: export type MutatorContext = { tx: Transaction authData: AuthData | null environment: 'server' | 'client' server?: { actions: ServerActions /** * Schedules work after the mutation transaction commits. Tasks run in the * background by default, following Zero's async task model, so they do not * block the push response. * * Set `barrier: true` only when the client's next writes depend on the * effect, such as provisioning a namespace before the client writes * through a new instance. Barrier tasks finish before the push response. */ enqueueTask(task: AsyncTask, opts?: AsyncTaskOptions): void enqueueAction(action: AsyncAction, opts?: AsyncTaskOptions): void } can: Can } // turns our mutators with custom context into zero mutators export type GetZeroMutators = { [Key in keyof Models]: TransformMutators[Key]> } type GetModelMutators = { [Key in keyof Models]: Models[Key]['mutate'] } export type GenericModels = { [key: string]: { mutate?: Record Promise> // eslint-disable-next-line typescript-eslint/no-unnecessary-type-arguments permissions?: Where } } export type TransformMutators = { [K in keyof T]: T[K] extends (ctx: MutatorContext, ...args: infer Args) => infer Return ? (tx: Transaction, ...args: Args) => Return extends unknown ? Promise : Return : never } export type Where< Table extends TableName = TableName, ReturnType extends Condition | boolean = Condition | boolean, > = ( expressionBuilder: ExpressionBuilder, auth?: AuthData | null ) => ReturnType export type Can = ( where: PWhere, obj: string | Record ) => Promise type GenericTable = TableBuilderWithColumns type GetTableSchema = TS extends TableBuilderWithColumns ? S : never // all non-optional keys required (but optional can be undefined) export type TableInsertRow = NullToOptional< Row> > // only primary keys required export type TableUpdateRow = Pick< Row>, TablePrimaryKeys > & Partial> export type TablePrimaryKeys = TupleToUnion< GetTableSchema['primaryKey'] > export type ZeroEvent = | { type: 'error' reasonKey: 'connection-error' | 'connection-needs-auth' message: string } | { type: 'reconnect' status: 'trying' | 'waiting' reasonKey: ZeroReconnectReasonKey reason: string } | { type: 'reconnect'; status: 'connected' } // destructive recovery lifecycle: 'recovering' = optionally dropping local // state + reloading; 'fatal' = the reload loop guard tripped. | { type: 'recovering'; reasonKey: ZeroRecoveryReasonKey; reason: string } | { type: 'fatal'; reasonKey: ZeroRecoveryReasonKey; reason: string } export type ZeroEventsEmitter = { value: ZeroEvent | null options?: { name: string silent?: boolean comparator?: (a: ZeroEvent | null, b: ZeroEvent | null) => boolean } listen: (listener: (value: ZeroEvent | null) => void) => () => void emit: (next: ZeroEvent | null) => void } export type ZeroRecoveryReasonKey = | 'NewClientGroup' | 'VersionNotSupported' | 'SchemaVersionNotSupported' | 'client-state-not-found' | 'indexeddb-not-found' | 'sqlite-statement-finalized' | 'store-closed-repeat' | 'mutation-desync' | 'connection-cookie-invalid' | 'client-not-found' | 'connection-userid-mismatch' export type ZeroReconnectReasonKey = | 'transport' | 'server-overloaded' | 'server-ack-timeout' /** * Admin role bypass for permissions: * - 'all': admin bypasses both query and mutation permissions (default) * - 'queries': admin bypasses only query permissions * - 'mutations': admin bypasses only mutation permissions * - 'off': admin has no special bypass */ export type AdminRoleMode = 'all' | 'queries' | 'mutations' | 'off'