@doync/client 0.1.0 → 0.1.1

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.
Files changed (42) hide show
  1. package/README.md +1 -17
  2. package/dist/adapter.cjs +1 -1
  3. package/dist/adapter.d.cts +1 -1
  4. package/dist/adapter.d.ts +1 -1
  5. package/dist/adapter.js +1 -1
  6. package/dist/adapter.js.map +1 -1
  7. package/dist/client-Bv6QMenB.cjs +15 -0
  8. package/dist/client-Db0Jfwuy.js +16 -0
  9. package/dist/client-Db0Jfwuy.js.map +1 -0
  10. package/dist/{client-CNyLMCw0.d.ts → client-OAVaC3pt.d.ts} +79 -91
  11. package/dist/client-OAVaC3pt.d.ts.map +1 -0
  12. package/dist/{client-DHXO0dbf.d.cts → client-OPt8Axyp.d.cts} +79 -91
  13. package/dist/client-OPt8Axyp.d.cts.map +1 -0
  14. package/dist/index.d.cts +3 -2
  15. package/dist/index.d.ts +3 -2
  16. package/dist/internal.cjs +1 -1
  17. package/dist/internal.d.cts +3 -12
  18. package/dist/internal.d.cts.map +1 -1
  19. package/dist/internal.d.ts +3 -12
  20. package/dist/internal.d.ts.map +1 -1
  21. package/dist/internal.js +1 -1
  22. package/package.json +3 -3
  23. package/src/client.ts +82 -32
  24. package/src/engine.ts +188 -134
  25. package/src/index.ts +1 -6
  26. package/src/internal.ts +4 -4
  27. package/src/migrate.ts +1 -1
  28. package/src/replica/db/0002_prefs_table.sql +9 -0
  29. package/src/replica/db/meta/0002_snapshot.json +191 -0
  30. package/src/replica/db/meta/_journal.json +7 -0
  31. package/src/replica/db/schema.ts +12 -3
  32. package/src/replica/index.ts +5 -4
  33. package/src/replica/meta.ts +23 -0
  34. package/src/replica/track.ts +20 -39
  35. package/src/socket-reconnect.ts +29 -5
  36. package/dist/client-C6jAdhbe.cjs +0 -15
  37. package/dist/client-CNyLMCw0.d.ts.map +0 -1
  38. package/dist/client-ClV8ce6X.js +0 -16
  39. package/dist/client-ClV8ce6X.js.map +0 -1
  40. package/dist/client-DHXO0dbf.d.cts.map +0 -1
  41. package/src/client-mutation-registry.ts +0 -31
  42. package/src/mutations.ts +0 -96
package/dist/index.d.cts CHANGED
@@ -1,2 +1,3 @@
1
- import { F as ClientMutation, I as ClientMutationHandler, L as ClientMutationRegistry, R as ClientMutationTx, _ as SchemaEventKind, b as ViewStatus, c as FalsyQuery, d as MutationResult, f as OnceView, g as SchemaEvent, h as QueryStatus, l as LogoutBehavior, m as PreloadOptions, o as ConnectionStatus, p as PreloadHandle, s as DoyncClient, u as MutationOptions, v as SubscribeOptions, y as View } from "./client-DHXO0dbf.cjs";
2
- export type { ClientMutation, ClientMutationHandler, ClientMutationRegistry, ClientMutationTx, ConnectionStatus, DoyncClient, FalsyQuery, LogoutBehavior, MutationOptions, MutationResult, OnceView, PreloadHandle, PreloadOptions, QueryStatus, SchemaEvent, SchemaEventKind, SubscribeOptions, View, ViewStatus };
1
+ import { _ as SchemaEvent, b as View, c as DoyncClient, d as MutationOptions, f as MutationResult, g as QueryStatus, h as PreloadOptions, l as FalsyQuery, m as PreloadHandle, p as OnceView, s as ConnectionStatus, u as LogoutBehavior, v as SchemaEventKind, x as ViewStatus, y as SubscribeOptions } from "./client-OPt8Axyp.cjs";
2
+ import { AuthData } from "@doync/core";
3
+ export type { AuthData, ConnectionStatus, DoyncClient, FalsyQuery, LogoutBehavior, MutationOptions, MutationResult, OnceView, PreloadHandle, PreloadOptions, QueryStatus, SchemaEvent, SchemaEventKind, SubscribeOptions, View, ViewStatus };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { F as ClientMutation, I as ClientMutationHandler, L as ClientMutationRegistry, R as ClientMutationTx, _ as SchemaEventKind, b as ViewStatus, c as FalsyQuery, d as MutationResult, f as OnceView, g as SchemaEvent, h as QueryStatus, l as LogoutBehavior, m as PreloadOptions, o as ConnectionStatus, p as PreloadHandle, s as DoyncClient, u as MutationOptions, v as SubscribeOptions, y as View } from "./client-CNyLMCw0.js";
2
- export type { ClientMutation, ClientMutationHandler, ClientMutationRegistry, ClientMutationTx, ConnectionStatus, DoyncClient, FalsyQuery, LogoutBehavior, MutationOptions, MutationResult, OnceView, PreloadHandle, PreloadOptions, QueryStatus, SchemaEvent, SchemaEventKind, SubscribeOptions, View, ViewStatus };
1
+ import { _ as SchemaEvent, b as View, c as DoyncClient, d as MutationOptions, f as MutationResult, g as QueryStatus, h as PreloadOptions, l as FalsyQuery, m as PreloadHandle, p as OnceView, s as ConnectionStatus, u as LogoutBehavior, v as SchemaEventKind, x as ViewStatus, y as SubscribeOptions } from "./client-OAVaC3pt.js";
2
+ import { AuthData } from "@doync/core";
3
+ export type { AuthData, ConnectionStatus, DoyncClient, FalsyQuery, LogoutBehavior, MutationOptions, MutationResult, OnceView, PreloadHandle, PreloadOptions, QueryStatus, SchemaEvent, SchemaEventKind, SubscribeOptions, View, ViewStatus };
package/dist/internal.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./client-C6jAdhbe.cjs");let t=require("@doync/sqlite-parser");function n(e){return encodeURIComponent(e).replaceAll(`_`,`%5F`)}function r(e,t){let r=n(e);return t===null?`doync__${r}__anon.db`:`doync__${r}__${n(t)}.db`}function i(e){let n;try{n=(0,t.parse)(e)}catch(t){return console.warn(`extractWriteTable: failed to parse SQL statement`,{sql:e,err:t}),null}switch(n.kind){case`STMT_INSERT`:case`STMT_UPDATE`:case`STMT_DELETE`:return n.table??null;default:return null}}exports.ClientEngine=e.r,exports.__CONNECTED_CLOCK_META_KEY=e.i,exports.__LOGOUT_BEHAVIOR_META_KEY=e.a,exports.applyBundledMigrations=e.l,exports.asClientMutation=e.v,exports.bundledSchemaDdl=e.h,exports.classifyClientStatement=e.g,exports.createClientEngine=e.n,exports.createEngineTables=e.u,exports.dbFileForUserId=r,exports.dropConsumerTables=e.d,exports.extractWriteTable=i,exports.isFalsyQuery=e.o,exports.normalizeQuerySurface=e.s,exports.readMeta=e.p,exports.replayMigrations=e.f,exports.settledRejection=e.c,exports.splitClientStatements=e._,exports.toClientMutationRegistry=e.b,exports.validateMutationArgs=e.y,exports.writeMeta=e.m;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./client-Bv6QMenB.cjs");let t=require("@doync/sqlite-parser");function n(e){return encodeURIComponent(e).replaceAll(`_`,`%5F`)}function r(e,t){let r=n(e);return t===null?`doync__${r}__anon.db`:`doync__${r}__${n(t)}.db`}function i(e){let n;try{n=(0,t.parse)(e)}catch(t){return console.warn(`extractWriteTable: failed to parse SQL statement`,{sql:e,err:t}),null}switch(n.kind){case`STMT_INSERT`:case`STMT_UPDATE`:case`STMT_DELETE`:return n.table??null;default:return null}}exports.ClientEngine=e.i,exports.__CONNECTED_CLOCK_META_KEY=e.a,exports.__LOGOUT_BEHAVIOR_PREF_KEY=e.o,exports.applyBundledMigrations=e.u,exports.bundledSchemaDdl=e.v,exports.classifyClientStatement=e.y,exports.createClientEngine=e.n,exports.createEngineTables=e.d,exports.dbFileForUserId=r,exports.dropConsumerTables=e.f,exports.extractWriteTable=i,exports.isFalsyQuery=e.s,exports.normalizeQuerySurface=e.c,exports.readMeta=e.m,exports.readPref=e.h,exports.replayMigrations=e.p,exports.resolveClientAuthData=e.r,exports.settledRejection=e.l,exports.splitClientStatements=e.b,exports.writeMeta=e.g,exports.writePref=e._;
@@ -1,15 +1,6 @@
1
- import { A as readMeta, B as validateMutationArgs, C as isFalsyQuery, D as createEngineTables, E as applyBundledMigrations, L as ClientMutationRegistry, O as dropConsumerTables, S as __LOGOUT_BEHAVIOR_META_KEY, T as settledRejection, U as extractWriteTable, a as ClientEngineConfig, i as ClientEngine, j as writeMeta, k as replayMigrations, r as createClientEngine, w as normalizeQuerySurface, x as __CONNECTED_CLOCK_META_KEY, z as asClientMutation } from "./client-DHXO0dbf.cjs";
2
- import { DoyncSchema, MutationTree } from "@doync/core";
1
+ import { A as replayMigrations, B as extractWriteTable, C as __LOGOUT_BEHAVIOR_PREF_KEY, D as applyBundledMigrations, E as settledRejection, M as readPref, N as writeMeta, O as createEngineTables, P as writePref, S as __CONNECTED_CLOCK_META_KEY, T as normalizeQuerySurface, a as ClientEngine, i as resolveClientAuthData, j as readMeta, k as dropConsumerTables, o as ClientEngineConfig, r as createClientEngine, w as isFalsyQuery } from "./client-OPt8Axyp.cjs";
2
+ import { DoyncSchema } from "@doync/core";
3
3
 
4
- //#region src/client-mutation-registry.d.ts
5
- /**
6
- * Flat `dottedName → {args, handler}` registry the client engine resolves
7
- * optimistic bodies against (ADR-0019). Feed the SHARED tree so no `.server.ts`
8
- * override enters the client bundle. Handler may return a promise (exclusive
9
- * chain awaits); engine validates args at `mutate()` via carried schema.
10
- */
11
- declare function toClientMutationRegistry(tree: MutationTree): ClientMutationRegistry;
12
- //#endregion
13
4
  //#region src/identity.d.ts
14
5
  /**
15
6
  * Durable replica file for one `(Database name, asserted userId)` pair:
@@ -65,5 +56,5 @@ declare function classifyClientStatement(statement: string): ClientStatementKind
65
56
  */
66
57
  declare function bundledSchemaDdl(schema: DoyncSchema, fromVersion: number, toVersion: number): string[];
67
58
  //#endregion
68
- export { ClientEngine, type ClientEngineConfig, type ClientStatementKind, __CONNECTED_CLOCK_META_KEY, __LOGOUT_BEHAVIOR_META_KEY, applyBundledMigrations, asClientMutation, bundledSchemaDdl, classifyClientStatement, createClientEngine, createEngineTables, dbFileForUserId, dropConsumerTables, extractWriteTable, isFalsyQuery, normalizeQuerySurface, readMeta, replayMigrations, settledRejection, splitClientStatements, toClientMutationRegistry, validateMutationArgs, writeMeta };
59
+ export { ClientEngine, type ClientEngineConfig, type ClientStatementKind, __CONNECTED_CLOCK_META_KEY, __LOGOUT_BEHAVIOR_PREF_KEY, applyBundledMigrations, bundledSchemaDdl, classifyClientStatement, createClientEngine, createEngineTables, dbFileForUserId, dropConsumerTables, extractWriteTable, isFalsyQuery, normalizeQuerySurface, readMeta, readPref, replayMigrations, resolveClientAuthData, settledRejection, splitClientStatements, writeMeta, writePref };
69
60
  //# sourceMappingURL=internal.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.cts","names":[],"sources":["../src/client-mutation-registry.ts","../src/identity.ts","../src/migrate.ts"],"mappings":";;;;;;;;AAqBG;;iBAFa,wBAAA,CACd,IAAA,EAAM,YAAA,GACL,sBAAA;;;;;;;AAFH;;;;iBCgBgB,eAAA,CAAgB,IAAA,UAAc,MAAA;;;;;;ADhB9C;;;;;KEAY,mBAAA;;;AFET;;;;ACcH;;;;AAA8C;;iBCgE9B,qBAAA,CAAsB,GAAA;;AAhFtC;;;;AAAY;AAgFZ;;iBAkBgB,uBAAA,CACd,SAAA,WACC,mBAAA;;AApBmC;AAkBtC;;;;AAEG;AAeH;;;iBAAgB,gBAAA,CACd,MAAA,EAAQ,WAAA,EACR,WAAA,UACA,SAAA"}
1
+ {"version":3,"file":"internal.d.cts","names":[],"sources":["../src/identity.ts","../src/migrate.ts"],"mappings":";;;;;;;;AAmCA;;;;iBAAgB,eAAA,CAAgB,IAAA,UAAc,MAAA;;;;;;AAA9C;;;;AAA8C;AAAA,KChBlC,mBAAA;;;AAAZ;;;;AAAY;AAgFZ;;;;AAAsC;iBAAtB,qBAAA,CAAsB,GAAA;;;;AAoBnC;AAeH;;;;iBAjBgB,uBAAA,CACd,SAAA,WACC,mBAAA;;;;;AAkBD;;;;;;iBAHc,gBAAA,CACd,MAAA,EAAQ,WAAA,EACR,WAAA,UACA,SAAA"}
@@ -1,15 +1,6 @@
1
- import { A as readMeta, B as validateMutationArgs, C as isFalsyQuery, D as createEngineTables, E as applyBundledMigrations, L as ClientMutationRegistry, O as dropConsumerTables, S as __LOGOUT_BEHAVIOR_META_KEY, T as settledRejection, U as extractWriteTable, a as ClientEngineConfig, i as ClientEngine, j as writeMeta, k as replayMigrations, r as createClientEngine, w as normalizeQuerySurface, x as __CONNECTED_CLOCK_META_KEY, z as asClientMutation } from "./client-CNyLMCw0.js";
2
- import { DoyncSchema, MutationTree } from "@doync/core";
1
+ import { A as replayMigrations, B as extractWriteTable, C as __LOGOUT_BEHAVIOR_PREF_KEY, D as applyBundledMigrations, E as settledRejection, M as readPref, N as writeMeta, O as createEngineTables, P as writePref, S as __CONNECTED_CLOCK_META_KEY, T as normalizeQuerySurface, a as ClientEngine, i as resolveClientAuthData, j as readMeta, k as dropConsumerTables, o as ClientEngineConfig, r as createClientEngine, w as isFalsyQuery } from "./client-OAVaC3pt.js";
2
+ import { DoyncSchema } from "@doync/core";
3
3
 
4
- //#region src/client-mutation-registry.d.ts
5
- /**
6
- * Flat `dottedName → {args, handler}` registry the client engine resolves
7
- * optimistic bodies against (ADR-0019). Feed the SHARED tree so no `.server.ts`
8
- * override enters the client bundle. Handler may return a promise (exclusive
9
- * chain awaits); engine validates args at `mutate()` via carried schema.
10
- */
11
- declare function toClientMutationRegistry(tree: MutationTree): ClientMutationRegistry;
12
- //#endregion
13
4
  //#region src/identity.d.ts
14
5
  /**
15
6
  * Durable replica file for one `(Database name, asserted userId)` pair:
@@ -65,5 +56,5 @@ declare function classifyClientStatement(statement: string): ClientStatementKind
65
56
  */
66
57
  declare function bundledSchemaDdl(schema: DoyncSchema, fromVersion: number, toVersion: number): string[];
67
58
  //#endregion
68
- export { ClientEngine, type ClientEngineConfig, type ClientStatementKind, __CONNECTED_CLOCK_META_KEY, __LOGOUT_BEHAVIOR_META_KEY, applyBundledMigrations, asClientMutation, bundledSchemaDdl, classifyClientStatement, createClientEngine, createEngineTables, dbFileForUserId, dropConsumerTables, extractWriteTable, isFalsyQuery, normalizeQuerySurface, readMeta, replayMigrations, settledRejection, splitClientStatements, toClientMutationRegistry, validateMutationArgs, writeMeta };
59
+ export { ClientEngine, type ClientEngineConfig, type ClientStatementKind, __CONNECTED_CLOCK_META_KEY, __LOGOUT_BEHAVIOR_PREF_KEY, applyBundledMigrations, bundledSchemaDdl, classifyClientStatement, createClientEngine, createEngineTables, dbFileForUserId, dropConsumerTables, extractWriteTable, isFalsyQuery, normalizeQuerySurface, readMeta, readPref, replayMigrations, resolveClientAuthData, settledRejection, splitClientStatements, writeMeta, writePref };
69
60
  //# sourceMappingURL=internal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","names":[],"sources":["../src/client-mutation-registry.ts","../src/identity.ts","../src/migrate.ts"],"mappings":";;;;;;;;AAqBG;;iBAFa,wBAAA,CACd,IAAA,EAAM,YAAA,GACL,sBAAA;;;;;;;AAFH;;;;iBCgBgB,eAAA,CAAgB,IAAA,UAAc,MAAA;;;;;;ADhB9C;;;;;KEAY,mBAAA;;;AFET;;;;ACcH;;;;AAA8C;;iBCgE9B,qBAAA,CAAsB,GAAA;;AAhFtC;;;;AAAY;AAgFZ;;iBAkBgB,uBAAA,CACd,SAAA,WACC,mBAAA;;AApBmC;AAkBtC;;;;AAEG;AAeH;;;iBAAgB,gBAAA,CACd,MAAA,EAAQ,WAAA,EACR,WAAA,UACA,SAAA"}
1
+ {"version":3,"file":"internal.d.ts","names":[],"sources":["../src/identity.ts","../src/migrate.ts"],"mappings":";;;;;;;;AAmCA;;;;iBAAgB,eAAA,CAAgB,IAAA,UAAc,MAAA;;;;;;AAA9C;;;;AAA8C;AAAA,KChBlC,mBAAA;;;AAAZ;;;;AAAY;AAgFZ;;;;AAAsC;iBAAtB,qBAAA,CAAsB,GAAA;;;;AAoBnC;AAeH;;;;iBAjBgB,uBAAA,CACd,SAAA,WACC,mBAAA;;;;;AAkBD;;;;;;iBAHc,gBAAA,CACd,MAAA,EAAQ,WAAA,EACR,WAAA,UACA,SAAA"}
package/dist/internal.js CHANGED
@@ -1,2 +1,2 @@
1
- import{_ as e,a as t,b as n,c as r,d as i,f as a,g as o,h as s,i as c,l,m as u,n as d,o as f,p,r as m,s as h,u as g,v as _,y as v}from"./client-ClV8ce6X.js";import{parse as y}from"@doync/sqlite-parser";function b(e){return encodeURIComponent(e).replaceAll(`_`,`%5F`)}function x(e,t){let n=b(e);return t===null?`doync__${n}__anon.db`:`doync__${n}__${b(t)}.db`}function S(e){let t;try{t=y(e)}catch(t){return console.warn(`extractWriteTable: failed to parse SQL statement`,{sql:e,err:t}),null}switch(t.kind){case`STMT_INSERT`:case`STMT_UPDATE`:case`STMT_DELETE`:return t.table??null;default:return null}}export{m as ClientEngine,c as __CONNECTED_CLOCK_META_KEY,t as __LOGOUT_BEHAVIOR_META_KEY,l as applyBundledMigrations,_ as asClientMutation,s as bundledSchemaDdl,o as classifyClientStatement,d as createClientEngine,g as createEngineTables,x as dbFileForUserId,i as dropConsumerTables,S as extractWriteTable,f as isFalsyQuery,h as normalizeQuerySurface,p as readMeta,a as replayMigrations,r as settledRejection,e as splitClientStatements,n as toClientMutationRegistry,v as validateMutationArgs,u as writeMeta};
1
+ import{_ as e,a as t,b as n,c as r,d as i,f as a,g as o,h as s,i as c,l,m as u,n as d,o as f,p,r as m,s as h,u as g,v as _,y as v}from"./client-Db0Jfwuy.js";import{parse as y}from"@doync/sqlite-parser";function b(e){return encodeURIComponent(e).replaceAll(`_`,`%5F`)}function x(e,t){let n=b(e);return t===null?`doync__${n}__anon.db`:`doync__${n}__${b(t)}.db`}function S(e){let t;try{t=y(e)}catch(t){return console.warn(`extractWriteTable: failed to parse SQL statement`,{sql:e,err:t}),null}switch(t.kind){case`STMT_INSERT`:case`STMT_UPDATE`:case`STMT_DELETE`:return t.table??null;default:return null}}export{c as ClientEngine,t as __CONNECTED_CLOCK_META_KEY,f as __LOGOUT_BEHAVIOR_PREF_KEY,g as applyBundledMigrations,_ as bundledSchemaDdl,v as classifyClientStatement,d as createClientEngine,i as createEngineTables,x as dbFileForUserId,a as dropConsumerTables,S as extractWriteTable,h as isFalsyQuery,r as normalizeQuerySurface,u as readMeta,s as readPref,p as replayMigrations,m as resolveClientAuthData,l as settledRejection,n as splitClientStatements,o as writeMeta,e as writePref};
2
2
  //# sourceMappingURL=internal.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doync/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "doync client engine: optimistic Layer-1 savepoint/rebase over a synchronous local-DB port",
5
5
  "keywords": [
6
6
  "client",
@@ -57,7 +57,7 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@doync/core": "0.1.0",
60
+ "@doync/core": "0.1.1",
61
61
  "@doync/sqlite-parser": "0.3053001.0"
62
62
  },
63
63
  "devDependencies": {
@@ -69,7 +69,7 @@
69
69
  "typescript": "^7.0.2",
70
70
  "unplugin-raw": "^0.7.0",
71
71
  "vitest": "4.1.9",
72
- "@doync/drizzle": "0.1.0"
72
+ "@doync/drizzle": "0.1.1"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "rm -rf dist && tsdown",
package/src/client.ts CHANGED
@@ -1,25 +1,32 @@
1
- import type { DoyncSchema, MutationTree } from '@doync/core'
1
+ import type {
2
+ AuthContext,
3
+ AuthData,
4
+ DoyncSchema,
5
+ MutationTree,
6
+ } from '@doync/core'
7
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
8
+
9
+ import { flattenMutations, validateStandardSchema } from '@doync/core/internal'
2
10
 
3
11
  import type { DoyncClient, LogoutBehavior, SchemaEvent } from './engine'
4
12
  import type { LocalDb } from './port'
5
13
  import type { SyncSocket } from './socket'
6
14
 
7
- import { toClientMutationRegistry } from './client-mutation-registry'
8
- import { ClientEngine, __LOGOUT_BEHAVIOR_META_KEY } from './engine'
9
- import { createEngineTables, writeMeta } from './replica'
15
+ import { __LOGOUT_BEHAVIOR_PREF_KEY, ClientEngine } from './engine'
16
+ import { createEngineTables, writePref } from './replica'
10
17
 
11
18
  // The durable logout-policy seam (ADR-0022) is owned by the engine (the layer
12
- // that owns `__doync_meta` and the runtime {@link ClientEngine.setLogoutBehavior}).
13
- // Only LogoutBehavior is public; the meta key stays on `./internal`.
19
+ // that owns `__doync_prefs` and the runtime {@link ClientEngine.setLogoutBehavior}).
20
+ // Only LogoutBehavior is public; the pref key stays on `./internal`.
14
21
  export type { LogoutBehavior } from './engine'
15
22
 
16
- // Identity is the asserted triple `{ token, ctx, userId }` — never derived
17
- // from a bearer token (ADR-0018 asserted-identity).
18
23
  /**
19
24
  * Options for {@link createClient}. Platform adapters (`@doync/web`,
20
25
  * `@doync/mobile`) fill these; app code rarely calls `createClient` directly.
21
26
  */
22
- export interface CreateClientOptions<TAuthData = unknown> {
27
+ export interface CreateClientOptions<
28
+ TAuthContext extends AuthContext = AuthContext,
29
+ > {
23
30
  /** Synchronous local SQLite port (wa-sqlite / node:sqlite / op-sqlite). */
24
31
  readonly db: LocalDb
25
32
  /** Synced schema shared with Origin and Mirror. */
@@ -32,22 +39,16 @@ export interface CreateClientOptions<TAuthData = unknown> {
32
39
  /** Transport to the Mirror. */
33
40
  readonly socket: SyncSocket
34
41
  /**
35
- * Bearer credential for the handshake. Does not drive `userId` or `ctx`. Omit
36
- * for cookie-session auth.
37
- */
38
- readonly token?: string
39
- /**
40
- * Projected auth context for optimistic query/mutation resolution (mirror
41
- * your server `toContext`). Defaults to `{}`.
42
+ * Whole client identity, or `null` for anonymous. `token` is optional inside
43
+ * {@link AuthData} (cookie auth); `ctx` is required when authenticated.
42
44
  */
43
- readonly ctx?: TAuthData
45
+ readonly authData: AuthData<TAuthContext> | null
44
46
  /**
45
- * Authenticated identity, or `null`/omit for anonymous. Never derived from
46
- * `token`.
47
+ * Standard Schema for `authData.ctx`. When present, construction validates
48
+ * synchronously and throws on failure. Platform adapters pass the consumer's
49
+ * shared `ctxValidationSchema`.
47
50
  */
48
- readonly userId?: string | null
49
- /** Pin a durable client id across restarts. */
50
- readonly clientId?: string
51
+ readonly ctxValidationSchema: StandardSchemaV1<unknown, TAuthContext>
51
52
  /** Observe schema/recovery transitions. */
52
53
  readonly onSchemaEvent?: (event: SchemaEvent) => void
53
54
  /**
@@ -63,38 +64,87 @@ export interface CreateClientOptions<TAuthData = unknown> {
63
64
  * Mirror socket. App code normally goes through `@doync/web` / `@doync/mobile`
64
65
  * instead.
65
66
  */
66
- export function createClient<TAuthData = unknown>(
67
- options: CreateClientOptions<TAuthData>,
67
+ export function createClient<TAuthContext extends AuthContext = AuthContext>(
68
+ options: CreateClientOptions<TAuthContext>,
68
69
  ): DoyncClient {
69
70
  return createClientEngine(options)
70
71
  }
71
72
 
73
+ /**
74
+ * {@link CreateClientOptions} for trusted internal construction: the web DB
75
+ * worker receives tab-validated identity over the wire and cannot hold the
76
+ * consumer's schema (a validator is a function), so the schema is optional here
77
+ * — absent means the caller already validated.
78
+ */
79
+ export type CreateClientEngineOptions<
80
+ TAuthContext extends AuthContext = AuthContext,
81
+ > = Omit<CreateClientOptions<TAuthContext>, 'ctxValidationSchema'> & {
82
+ readonly ctxValidationSchema?: StandardSchemaV1<unknown, TAuthContext>
83
+ /** Pin a durable client id (monorepo test/bench lanes only). */
84
+ readonly clientId?: string
85
+ }
86
+
72
87
  /**
73
88
  * Concrete-engine factory (ADR-0033 / closeio/doync#241). Same construction as
74
89
  * {@link createClient}, but typed as {@link ClientEngine} so platform adapters
75
90
  * (web DB-worker, mobile wrapper) can reach engine-only fields like `clientId`
76
91
  * without an unchecked downcast. Exported on `@doync/client/internal` only.
77
92
  */
78
- export function createClientEngine<TAuthData = unknown>(
79
- options: CreateClientOptions<TAuthData>,
80
- ): ClientEngine {
93
+ export function createClientEngine<
94
+ TAuthContext extends AuthContext = AuthContext,
95
+ >(options: CreateClientEngineOptions<TAuthContext>): ClientEngine {
81
96
  // Logout policy must land before boot (ADR-0022 / #135): construction opens
82
97
  // the optimistic overlay (ADR-0019), so a later write on this connection would
83
98
  // join the savepoint and roll back. Pre-boot is autocommit.
84
99
  if (options.logoutBehavior !== undefined) {
85
100
  createEngineTables(options.db)
86
- writeMeta(options.db, __LOGOUT_BEHAVIOR_META_KEY, options.logoutBehavior)
101
+ writePref(options.db, __LOGOUT_BEHAVIOR_PREF_KEY, options.logoutBehavior)
87
102
  }
103
+ const identity = resolveClientAuthData(
104
+ options.authData,
105
+ options.ctxValidationSchema,
106
+ )
88
107
  return new ClientEngine({
89
108
  db: options.db,
90
109
  schema: options.schema,
91
- mutations: toClientMutationRegistry(options.mutations),
110
+ mutations: flattenMutations(options.mutations),
92
111
  socket: options.socket,
93
112
  // Asserted identity (ADR-0018 / #167): never derived.
94
- ctx: (options.ctx as Record<string, unknown> | undefined) ?? {},
95
- token: options.token,
96
- userId: options.userId ?? null,
113
+ ctx: identity.ctx,
114
+ token: identity.token,
115
+ userId: identity.userId,
97
116
  clientId: options.clientId,
98
117
  onSchemaEvent: options.onSchemaEvent,
99
118
  })
100
119
  }
120
+
121
+ /**
122
+ * Validate and flatten `authData` for engine slots. When a schema is present
123
+ * and identity is non-null, `ctx` is validated synchronously (throws at the
124
+ * call site). Anonymous (`null`) yields `userId: null`, `ctx: null`, no token.
125
+ */
126
+ export function resolveClientAuthData<TAuthContext extends AuthContext>(
127
+ authData: AuthData<TAuthContext> | null,
128
+ ctxValidationSchema?: StandardSchemaV1<unknown, TAuthContext>,
129
+ ): {
130
+ userId: string | null
131
+ token: string | undefined
132
+ ctx: AuthContext
133
+ } {
134
+ if (authData === null) {
135
+ return { userId: null, token: undefined, ctx: null }
136
+ }
137
+ let ctx: Record<string, unknown> = authData.ctx
138
+ if (ctxValidationSchema !== undefined) {
139
+ ctx = validateStandardSchema(
140
+ ctxValidationSchema,
141
+ authData.ctx,
142
+ 'auth context',
143
+ ) as Record<string, unknown>
144
+ }
145
+ return {
146
+ userId: authData.userId,
147
+ token: authData.token,
148
+ ctx,
149
+ }
150
+ }