on-zero 0.0.87 → 0.0.88

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 (55) hide show
  1. package/dist/cjs/createZeroClient.cjs +32 -15
  2. package/dist/cjs/createZeroClient.js +42 -20
  3. package/dist/cjs/createZeroClient.js.map +1 -1
  4. package/dist/cjs/createZeroClient.native.js +61 -25
  5. package/dist/cjs/createZeroClient.native.js.map +1 -1
  6. package/dist/cjs/createZeroServer.cjs +3 -3
  7. package/dist/cjs/createZeroServer.js +2 -2
  8. package/dist/cjs/createZeroServer.js.map +1 -1
  9. package/dist/cjs/createZeroServer.native.js +3 -3
  10. package/dist/cjs/createZeroServer.native.js.map +1 -1
  11. package/dist/cjs/modelRegistry.cjs +4 -0
  12. package/dist/cjs/modelRegistry.js +4 -0
  13. package/dist/cjs/modelRegistry.js.map +1 -1
  14. package/dist/cjs/modelRegistry.native.js +4 -0
  15. package/dist/cjs/modelRegistry.native.js.map +1 -1
  16. package/dist/cjs/where.cjs +6 -1
  17. package/dist/cjs/where.js +6 -1
  18. package/dist/cjs/where.js.map +1 -1
  19. package/dist/cjs/where.native.js +7 -2
  20. package/dist/cjs/where.native.js.map +1 -1
  21. package/dist/esm/createZeroClient.js +44 -22
  22. package/dist/esm/createZeroClient.js.map +1 -1
  23. package/dist/esm/createZeroClient.mjs +34 -17
  24. package/dist/esm/createZeroClient.mjs.map +1 -1
  25. package/dist/esm/createZeroClient.native.js +63 -27
  26. package/dist/esm/createZeroClient.native.js.map +1 -1
  27. package/dist/esm/createZeroServer.js +2 -2
  28. package/dist/esm/createZeroServer.js.map +1 -1
  29. package/dist/esm/createZeroServer.mjs +3 -3
  30. package/dist/esm/createZeroServer.mjs.map +1 -1
  31. package/dist/esm/createZeroServer.native.js +3 -3
  32. package/dist/esm/createZeroServer.native.js.map +1 -1
  33. package/dist/esm/modelRegistry.js +4 -0
  34. package/dist/esm/modelRegistry.js.map +1 -1
  35. package/dist/esm/modelRegistry.mjs +4 -1
  36. package/dist/esm/modelRegistry.mjs.map +1 -1
  37. package/dist/esm/modelRegistry.native.js +4 -1
  38. package/dist/esm/modelRegistry.native.js.map +1 -1
  39. package/dist/esm/where.js +6 -1
  40. package/dist/esm/where.js.map +1 -1
  41. package/dist/esm/where.mjs +6 -2
  42. package/dist/esm/where.mjs.map +1 -1
  43. package/dist/esm/where.native.js +6 -2
  44. package/dist/esm/where.native.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/createZeroClient.tsx +80 -37
  47. package/src/createZeroServer.ts +4 -4
  48. package/src/modelRegistry.ts +4 -0
  49. package/src/where.ts +9 -1
  50. package/types/createZeroClient.d.ts +3 -1
  51. package/types/createZeroClient.d.ts.map +1 -1
  52. package/types/modelRegistry.d.ts +1 -0
  53. package/types/modelRegistry.d.ts.map +1 -1
  54. package/types/where.d.ts +1 -0
  55. package/types/where.d.ts.map +1 -1
package/dist/esm/where.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { isServer } from "./constants";
2
2
  import { getQueryOrMutatorAuthData } from "./helpers/getQueryOrMutatorAuthData";
3
+ let _evaluatingPermission = !1;
4
+ function setEvaluatingPermission(value) {
5
+ _evaluatingPermission = value;
6
+ }
3
7
  function where(a, b, isServerOnly = !1) {
4
8
  const whereFn = b || a, wrappedWhereFn = ((a2, b2 = getQueryOrMutatorAuthData()) => {
5
- if (!isServer && isServerOnly)
9
+ if (!isServer && isServerOnly && !_evaluatingPermission)
6
10
  return a2.and();
7
11
  const result = whereFn(a2, b2);
8
12
  return typeof result == "boolean" ? result ? a2.cmpLit(0, "=", 0) : a2.cmpLit(1, "=", 0) : result;
@@ -19,6 +23,7 @@ function getRawWhere(where2) {
19
23
  export {
20
24
  getRawWhere,
21
25
  getWhereTableName,
26
+ setEvaluatingPermission,
22
27
  where
23
28
  };
24
29
  //# sourceMappingURL=where.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/where.ts"],
4
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,iCAAiC;AAenC,SAAS,MACd,GACA,GACA,eAAe,IACc;AAC7B,QAAM,UAAW,KAAK,GAEhB,kBAAkB,CACtBA,IACAC,KAAI,0BAA0B,MAC3B;AACH,QAAI,CAAC,YAAY;AAEf,aAAOD,GAAE,IAAI;AAGf,UAAM,SAAS,QAAQA,IAAGC,EAAC;AAC3B,WAAI,OAAO,UAAW,YAChB,SACKD,GAAE,OAAO,GAAG,KAAK,CAAC,IAElBA,GAAE,OAAO,GAAG,KAAK,CAAC,IAGtB;AAAA,EACT;AAGA,4BAAmB,IAAI,gBAAgB,OAAO,GAE1C,KACF,kBAAkB,IAAI,gBAAgB,CAAU,GAG3C;AACT;AAIA,MAAM,oBAAoB,oBAAI,QAA0B,GAClD,qBAAqB,oBAAI,QAAsB;AAE9C,SAAS,kBAAkBE,QAAc;AAC9C,SAAO,kBAAkB,IAAIA,MAAK;AACpC;AAGO,SAAS,YAAYA,QAAiC;AAC3D,SAAO,mBAAmB,IAAIA,MAAK;AACrC;",
4
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,iCAAiC;AAO1C,IAAI,wBAAwB;AAErB,SAAS,wBAAwB,OAAgB;AACtD,0BAAwB;AAC1B;AAYO,SAAS,MACd,GACA,GACA,eAAe,IACc;AAC7B,QAAM,UAAW,KAAK,GAEhB,kBAAkB,CACtBA,IACAC,KAAI,0BAA0B,MAC3B;AACH,QAAI,CAAC,YAAY,gBAAgB,CAAC;AAEhC,aAAOD,GAAE,IAAI;AAGf,UAAM,SAAS,QAAQA,IAAGC,EAAC;AAC3B,WAAI,OAAO,UAAW,YAChB,SACKD,GAAE,OAAO,GAAG,KAAK,CAAC,IAElBA,GAAE,OAAO,GAAG,KAAK,CAAC,IAGtB;AAAA,EACT;AAGA,4BAAmB,IAAI,gBAAgB,OAAO,GAE1C,KACF,kBAAkB,IAAI,gBAAgB,CAAU,GAG3C;AACT;AAIA,MAAM,oBAAoB,oBAAI,QAA0B,GAClD,qBAAqB,oBAAI,QAAsB;AAE9C,SAAS,kBAAkBE,QAAc;AAC9C,SAAO,kBAAkB,IAAIA,MAAK;AACpC;AAGO,SAAS,YAAYA,QAAiC;AAC3D,SAAO,mBAAmB,IAAIA,MAAK;AACrC;",
5
5
  "names": ["a", "b", "where"]
6
6
  }
@@ -1,9 +1,13 @@
1
1
  import { isServer } from "./constants.mjs";
2
2
  import { getQueryOrMutatorAuthData } from "./helpers/getQueryOrMutatorAuthData.mjs";
3
+ let _evaluatingPermission = !1;
4
+ function setEvaluatingPermission(value) {
5
+ _evaluatingPermission = value;
6
+ }
3
7
  function where(a, b, isServerOnly = !1) {
4
8
  const whereFn = b || a,
5
9
  wrappedWhereFn = (a2, b2 = getQueryOrMutatorAuthData()) => {
6
- if (!isServer && isServerOnly) return a2.and();
10
+ if (!isServer && isServerOnly && !_evaluatingPermission) return a2.and();
7
11
  const result = whereFn(a2, b2);
8
12
  return typeof result == "boolean" ? result ? a2.cmpLit(0, "=", 0) : a2.cmpLit(1, "=", 0) : result;
9
13
  };
@@ -17,5 +21,5 @@ function getWhereTableName(where2) {
17
21
  function getRawWhere(where2) {
18
22
  return WhereRawBuilderMap.get(where2);
19
23
  }
20
- export { getRawWhere, getWhereTableName, where };
24
+ export { getRawWhere, getWhereTableName, setEvaluatingPermission, where };
21
25
  //# sourceMappingURL=where.mjs.map
@@ -1 +1 @@
1
- {"version":3,"names":["isServer","getQueryOrMutatorAuthData","where","a","b","isServerOnly","whereFn","wrappedWhereFn","a2","b2","and","result","cmpLit","WhereRawBuilderMap","set","WhereTableNameMap","WeakMap","getWhereTableName","where2","get","getRawWhere"],"sources":["../../src/where.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,QAAgB;AACzB,SAASC,yBAAA,QAAiC;AAenC,SAASC,MACdC,CAAA,EACAC,CAAA,EACAC,YAAA,GAAe,IACc;EAC7B,MAAMC,OAAA,GAAWF,CAAA,IAAKD,CAAA;IAEhBI,cAAA,GAAkBA,CACtBC,EAAA,EACAC,EAAA,GAAIR,yBAAA,CAA0B,MAC3B;MACH,IAAI,CAACD,QAAA,IAAYK,YAAA,EAEf,OAAOG,EAAA,CAAEE,GAAA,CAAI;MAGf,MAAMC,MAAA,GAASL,OAAA,CAAQE,EAAA,EAAGC,EAAC;MAC3B,OAAI,OAAOE,MAAA,IAAW,YAChBA,MAAA,GACKH,EAAA,CAAEI,MAAA,CAAO,GAAG,KAAK,CAAC,IAElBJ,EAAA,CAAEI,MAAA,CAAO,GAAG,KAAK,CAAC,IAGtBD,MAAA;IACT;EAGA,OAAAE,kBAAA,CAAmBC,GAAA,CAAIP,cAAA,EAAgBD,OAAO,GAE1CF,CAAA,IACFW,iBAAA,CAAkBD,GAAA,CAAIP,cAAA,EAAgBJ,CAAU,GAG3CI,cAAA;AACT;AAIA,MAAMQ,iBAAA,GAAoB,mBAAIC,OAAA,CAA0B;EAClDH,kBAAA,GAAqB,mBAAIG,OAAA,CAAsB;AAE9C,SAASC,kBAAkBC,MAAA,EAAc;EAC9C,OAAOH,iBAAA,CAAkBI,GAAA,CAAID,MAAK;AACpC;AAGO,SAASE,YAAYF,MAAA,EAAiC;EAC3D,OAAOL,kBAAA,CAAmBM,GAAA,CAAID,MAAK;AACrC","ignoreList":[]}
1
+ {"version":3,"names":["isServer","getQueryOrMutatorAuthData","_evaluatingPermission","setEvaluatingPermission","value","where","a","b","isServerOnly","whereFn","wrappedWhereFn","a2","b2","and","result","cmpLit","WhereRawBuilderMap","set","WhereTableNameMap","WeakMap","getWhereTableName","where2","get","getRawWhere"],"sources":["../../src/where.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,QAAgB;AACzB,SAASC,yBAAA,QAAiC;AAO1C,IAAIC,qBAAA,GAAwB;AAErB,SAASC,wBAAwBC,KAAA,EAAgB;EACtDF,qBAAA,GAAwBE,KAAA;AAC1B;AAYO,SAASC,MACdC,CAAA,EACAC,CAAA,EACAC,YAAA,GAAe,IACc;EAC7B,MAAMC,OAAA,GAAWF,CAAA,IAAKD,CAAA;IAEhBI,cAAA,GAAkBA,CACtBC,EAAA,EACAC,EAAA,GAAIX,yBAAA,CAA0B,MAC3B;MACH,IAAI,CAACD,QAAA,IAAYQ,YAAA,IAAgB,CAACN,qBAAA,EAEhC,OAAOS,EAAA,CAAEE,GAAA,CAAI;MAGf,MAAMC,MAAA,GAASL,OAAA,CAAQE,EAAA,EAAGC,EAAC;MAC3B,OAAI,OAAOE,MAAA,IAAW,YAChBA,MAAA,GACKH,EAAA,CAAEI,MAAA,CAAO,GAAG,KAAK,CAAC,IAElBJ,EAAA,CAAEI,MAAA,CAAO,GAAG,KAAK,CAAC,IAGtBD,MAAA;IACT;EAGA,OAAAE,kBAAA,CAAmBC,GAAA,CAAIP,cAAA,EAAgBD,OAAO,GAE1CF,CAAA,IACFW,iBAAA,CAAkBD,GAAA,CAAIP,cAAA,EAAgBJ,CAAU,GAG3CI,cAAA;AACT;AAIA,MAAMQ,iBAAA,GAAoB,mBAAIC,OAAA,CAA0B;EAClDH,kBAAA,GAAqB,mBAAIG,OAAA,CAAsB;AAE9C,SAASC,kBAAkBC,MAAA,EAAc;EAC9C,OAAOH,iBAAA,CAAkBI,GAAA,CAAID,MAAK;AACpC;AAGO,SAASE,YAAYF,MAAA,EAAiC;EAC3D,OAAOL,kBAAA,CAAmBM,GAAA,CAAID,MAAK;AACrC","ignoreList":[]}
@@ -1,11 +1,15 @@
1
1
  import { isServer } from "./constants.native.js";
2
2
  import { getQueryOrMutatorAuthData } from "./helpers/getQueryOrMutatorAuthData.native.js";
3
+ var _evaluatingPermission = !1;
4
+ function setEvaluatingPermission(value) {
5
+ _evaluatingPermission = value;
6
+ }
3
7
  function where(a, b) {
4
8
  var isServerOnly = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : !1,
5
9
  whereFn = b || a,
6
10
  wrappedWhereFn = function (a2) {
7
11
  var _$b = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : getQueryOrMutatorAuthData();
8
- if (!isServer && isServerOnly) return a2.and();
12
+ if (!isServer && isServerOnly && !_evaluatingPermission) return a2.and();
9
13
  var result = whereFn(a2, _$b);
10
14
  return typeof result == "boolean" ? result ? a2.cmpLit(0, "=", 0) : a2.cmpLit(1, "=", 0) : result;
11
15
  };
@@ -19,5 +23,5 @@ function getWhereTableName(where2) {
19
23
  function getRawWhere(where2) {
20
24
  return WhereRawBuilderMap.get(where2);
21
25
  }
22
- export { getRawWhere, getWhereTableName, where };
26
+ export { getRawWhere, getWhereTableName, setEvaluatingPermission, where };
23
27
  //# sourceMappingURL=where.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["isServer","getQueryOrMutatorAuthData","where","a","b","isServerOnly","arguments","length","whereFn","wrappedWhereFn","a2","_$b","and","result","cmpLit","WhereRawBuilderMap","set","WhereTableNameMap","WeakMap","getWhereTableName","where2","get","getRawWhere"],"sources":["../../src/where.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,QAAgB;AACzB,SAASC,yBAAA,QAAiC;AAenC,SAASC,MACdC,CAAA,EACAC,CAAA,EACA;EAEA,IAAAC,YAAiB,GAAAC,SAEX,CAAAC,MAAA,QACJD,SACI,iBAAAA,SAA0B,MAC3B;IAAAE,OAAA,GAAAJ,CAAA,IAAAD,CAAA;IAAAM,cAAA,YAAAA,CAAAC,EAAA;MACH,IAAIC,GAAC,GAAAL,SAAY,CAAAC,MAAA,QAAAD,SAAA,iBAAAA,SAAA,MAAAL,yBAAA;MAEf,KAAAD,QAAS,IAAIK,YAAA,EAGf,OAAMK,EAAA,CAAAE,GAAS;MACf,IAAAC,MAAI,GAAAL,OAAO,CAAAE,EAAA,EAAWC,GAAA;MAQxB,cAAAE,MAAA,gBAAAA,MAAA,GAAAH,EAAA,CAAAI,MAAA,cAAAJ,EAAA,CAAAI,MAAA,cAAAD,MAAA;IAGA;EAOF,OAAAE,kBAAA,CAAAC,GAAA,CAAAP,cAAA,EAAAD,OAAA,GAAAJ,CAAA,IAAAa,iBAAA,CAAAD,GAAA,CAAAP,cAAA,EAAAN,CAAA,GAAAM,cAAA;AAIA;AAGO,IAAAQ,iBAAS,kBAAgC,IAAAC,OAAA;EAAAH,kBAAA,sBAAAG,OAAA;AAC9C,SAAOC,kBAAkBC,MAAI;EAC/B,OAAAH,iBAAA,CAAAI,GAAA,CAAAD,MAAA;AAGO;AACL,SAAOE,YAAAF,MAAA,EAAmB;EAC5B,OAAAL,kBAAA,CAAAM,GAAA,CAAAD,MAAA","ignoreList":[]}
1
+ {"version":3,"names":["isServer","getQueryOrMutatorAuthData","_evaluatingPermission","setEvaluatingPermission","value","where","a","b","isServerOnly","arguments","length","whereFn","wrappedWhereFn","a2","_$b","and","result","cmpLit","WhereRawBuilderMap","set","WhereTableNameMap","WeakMap","getWhereTableName","where2","get","getRawWhere"],"sources":["../../src/where.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,QAAgB;AACzB,SAASC,yBAAA,QAAiC;AAO1C,IAAIC,qBAAA,GAAwB;AAErB,SAASC,wBAAwBC,KAAA,EAAgB;EACtDF,qBAAA,GAAwBE,KAAA;AAC1B;AAYO,SAASC,MACdC,CAAA,EACAC,CAAA,EACA;EAEA,IAAAC,YAAiB,GAAAC,SAEX,CAAAC,MAAA,QACJD,SACI,iBAAAA,SAA0B,MAC3B;IAAAE,OAAA,GAAAJ,CAAA,IAAAD,CAAA;IAAAM,cAAA,YAAAA,CAAAC,EAAA;MACH,IAAIC,GAAC,GAAAL,SAAY,CAAAC,MAAA,QAAiBD,SAAA,iBAAAA,SAAA,MAAAR,yBAAA;MAEhC,KAAAD,QAAS,IAAIQ,YAAA,KAAAN,qBAAA,EAGf,OAAMW,EAAA,CAAAE,GAAS;MACf,IAAAC,MAAI,GAAAL,OAAO,CAAAE,EAAA,EAAWC,GAAA;MAQxB,cAAAE,MAAA,gBAAAA,MAAA,GAAAH,EAAA,CAAAI,MAAA,cAAAJ,EAAA,CAAAI,MAAA,cAAAD,MAAA;IAGA;EAOF,OAAAE,kBAAA,CAAAC,GAAA,CAAAP,cAAA,EAAAD,OAAA,GAAAJ,CAAA,IAAAa,iBAAA,CAAAD,GAAA,CAAAP,cAAA,EAAAN,CAAA,GAAAM,cAAA;AAIA;AAGO,IAAAQ,iBAAS,kBAAgC,IAAAC,OAAA;EAAAH,kBAAA,sBAAAG,OAAA;AAC9C,SAAOC,kBAAkBC,MAAI;EAC/B,OAAAH,iBAAA,CAAAI,GAAA,CAAAD,MAAA;AAGO;AACL,SAAOE,YAAAF,MAAA,EAAmB;EAC5B,OAAAL,kBAAA,CAAAM,GAAA,CAAAD,MAAA","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "on-zero",
3
- "version": "0.0.87",
3
+ "version": "0.0.88",
4
4
  "description": "A typed layer over @rocicorp/zero with queries, mutations, and permissions",
5
5
  "sideEffects": false,
6
6
  "source": "src/index.ts",
@@ -15,12 +15,12 @@ import { createPermissions } from './createPermissions'
15
15
  import { createUseQuery } from './createUseQuery'
16
16
  import { createMutators } from './helpers/createMutators'
17
17
  import { getQueryOrMutatorAuthData } from './helpers/getQueryOrMutatorAuthData'
18
- import { getMutationsPermissions } from './modelRegistry'
18
+ import { getAllMutationsPermissions, getMutationsPermissions } from './modelRegistry'
19
19
  import { registerQuery } from './queryRegistry'
20
20
  import { resolveQuery, type PlainQueryFn } from './resolveQuery'
21
21
  import { setCustomQueries } from './run'
22
22
  import { setAuthData, setSchema } from './state'
23
- import { getRawWhere } from './where'
23
+ import { getRawWhere, setEvaluatingPermission } from './where'
24
24
  import { setRunner } from './zeroRunner'
25
25
  import { zql } from './zql'
26
26
 
@@ -31,6 +31,12 @@ type PreloadOptions = { ttl?: 'always' | 'never' | number | undefined }
31
31
 
32
32
  export type GroupedQueries = Record<string, Record<string, (...args: any[]) => any>>
33
33
 
34
+ // controls how usePermission behaves before the server responds:
35
+ // - 'optimistic': evaluate the permission query on the client (default)
36
+ // - 'optimistic-deny': return false until server confirms
37
+ // - 'optimistic-allow': return true until server confirms
38
+ export type PermissionStrategy = 'optimistic' | 'optimistic-deny' | 'optimistic-allow'
39
+
34
40
  export function createZeroClient<
35
41
  Schema extends ZeroSchema,
36
42
  Models extends GenericModels,
@@ -38,10 +44,12 @@ export function createZeroClient<
38
44
  schema,
39
45
  models,
40
46
  groupedQueries,
47
+ permissionStrategy = 'optimistic',
41
48
  }: {
42
49
  schema: Schema
43
50
  models: Models
44
51
  groupedQueries: GroupedQueries
52
+ permissionStrategy?: PermissionStrategy
45
53
  }) {
46
54
  type ZeroMutators = GetZeroMutators<Models>
47
55
  type ZeroInstance = Zero<Schema, ZeroMutators>
@@ -74,41 +82,74 @@ export function createZeroClient<
74
82
  }
75
83
  }
76
84
 
77
- // register permission.check synced query
78
- // client: evaluates raw permission condition for optimistic result (zero caches via materialization)
85
+ // register per-model permission queries so each table gets its own materialized view
86
+ // client: evaluates raw permission condition for optimistic result
79
87
  // server: evaluates real permission condition authoritatively
80
- const permissionCheckFn = (args: {
81
- table: string
82
- objOrId: string | Record<string, any>
83
- }) => {
84
- const perm = getMutationsPermissions(args.table)
85
- const base = (zql as any)[args.table]
88
+ const permissionCheckFns: Record<
89
+ string,
90
+ (args: { objOrId: string | Record<string, any> }) => any
91
+ > = {}
86
92
 
87
- // when objOrId is missing, return a query that matches nothing
88
- if (!args.objOrId) {
89
- return base.where((eb: any) => eb.cmpLit(true, '=', false)).one()
90
- }
93
+ const createPermissionCheckFn = (table: string) => {
94
+ const fn = (args: { objOrId: string | Record<string, any> }) => {
95
+ const perm = getMutationsPermissions(table)
96
+ const base = (zql as any)[table]
91
97
 
92
- // use the raw (unwrapped) builder so serverWhere conditions actually evaluate on client
93
- // this gives us optimistic permission results — zero caches via its materialized views
94
- const rawPerm = perm ? getRawWhere(perm) || perm : perm
95
-
96
- return base
97
- .where((eb: any) => {
98
- return permissionsHelpers.buildPermissionQuery(
99
- getQueryOrMutatorAuthData(),
100
- eb,
101
- rawPerm || ((e: any) => e.and()),
102
- args.objOrId,
103
- args.table
104
- )
105
- })
106
- .one()
98
+ if (!args.objOrId) {
99
+ return base.where((eb: any) => eb.cmpLit(true, '=', false)).one()
100
+ }
101
+
102
+ if (permissionStrategy === 'optimistic') {
103
+ // unwrap serverWhere so conditions actually evaluate on client
104
+ // set flag so nested serverWhere calls also bypass the client no-op
105
+ const rawPerm = perm ? getRawWhere(perm) || perm : perm
106
+ return base
107
+ .where((eb: any) => {
108
+ setEvaluatingPermission(true)
109
+ try {
110
+ return permissionsHelpers.buildPermissionQuery(
111
+ getQueryOrMutatorAuthData(),
112
+ eb,
113
+ rawPerm || ((e: any) => e.and()),
114
+ args.objOrId,
115
+ table
116
+ )
117
+ } finally {
118
+ setEvaluatingPermission(false)
119
+ }
120
+ })
121
+ .one()
122
+ }
123
+
124
+ if (permissionStrategy === 'optimistic-deny') {
125
+ // client query always returns false, server corrects authoritatively
126
+ return base.where((eb: any) => eb.cmpLit(true, '=', false)).one()
127
+ }
128
+
129
+ // optimistic-allow: pass wrapped perm directly
130
+ // serverWhere is a no-op on client → eb.and() → always true → row exists check
131
+ // server evaluates real condition and corrects authoritatively
132
+ return base
133
+ .where((eb: any) => {
134
+ return permissionsHelpers.buildPermissionQuery(
135
+ getQueryOrMutatorAuthData(),
136
+ eb,
137
+ perm || ((e: any) => e.and()),
138
+ args.objOrId,
139
+ table
140
+ )
141
+ })
142
+ .one()
143
+ }
144
+ permissionCheckFns[table] = fn
145
+ registerQuery(fn, `permission.${table}`)
146
+ return fn
107
147
  }
108
148
 
109
- registerQuery(permissionCheckFn, 'permission.check')
110
- wrappedNamespaces['permission'] = {
111
- check: defineQuery(({ args }: any) => permissionCheckFn(args)),
149
+ wrappedNamespaces['permission'] = {}
150
+ for (const [table] of getAllMutationsPermissions()) {
151
+ const fn = createPermissionCheckFn(table)
152
+ wrappedNamespaces['permission'][table] = defineQuery(({ args }: any) => fn(args))
112
153
  }
113
154
 
114
155
  // create the single shared CustomQuery registry
@@ -143,8 +184,8 @@ export function createZeroClient<
143
184
  customQueries,
144
185
  })
145
186
 
146
- // permission check uses a synced query so server is authoritative
147
- // client is optimistic (serverWhere is no-op), server evaluates real condition
187
+ // permission check uses a per-model synced query so server is authoritative
188
+ // permissionStrategy controls client behavior before server responds
148
189
  function usePermission(
149
190
  table: TableName | (string & {}),
150
191
  objOrId: string | Partial<Row<any>> | undefined,
@@ -152,11 +193,13 @@ export function createZeroClient<
152
193
  debug = false
153
194
  ): boolean | null {
154
195
  const disabled = use(DisabledContext)
196
+ const tableStr = table as string
197
+ const checkFn = permissionCheckFns[tableStr]
155
198
 
156
199
  const [data, status] = useQuery(
157
- permissionCheckFn as any,
158
- { table: table as string, objOrId: objOrId as any },
159
- { enabled: Boolean(!disabled && enabled && objOrId) }
200
+ checkFn as any,
201
+ { objOrId: objOrId as any },
202
+ { enabled: Boolean(!disabled && enabled && objOrId && checkFn) }
160
203
  )
161
204
 
162
205
  if (debug) {
@@ -132,10 +132,10 @@ export function createZeroServer<
132
132
 
133
133
  const response = await zeroHandleQueryRequest(
134
134
  (name, args) => {
135
- // permission.check is registered by on-zero at runtime, not in the user's query registry
136
- if (name === 'permission.check') {
137
- const { table, objOrId } = args as {
138
- table: string
135
+ // per-model permission queries registered by on-zero at runtime
136
+ if (name.startsWith('permission.')) {
137
+ const table = name.slice('permission.'.length)
138
+ const { objOrId } = args as {
139
139
  objOrId: string | Record<string, any>
140
140
  }
141
141
  const perm = getMutationsPermissions(table)
@@ -9,3 +9,7 @@ export function setMutationsPermissions(tableName: string, permissions: Where) {
9
9
  export function getMutationsPermissions(tableName: string): Where | undefined {
10
10
  return mutationsToPermissionsRegistry.get(tableName)
11
11
  }
12
+
13
+ export function getAllMutationsPermissions(): Map<string, Where> {
14
+ return mutationsToPermissionsRegistry
15
+ }
package/src/where.ts CHANGED
@@ -4,6 +4,14 @@ import { getQueryOrMutatorAuthData } from './helpers/getQueryOrMutatorAuthData'
4
4
  import type { TableName, Where } from './types'
5
5
  import type { Condition, ExpressionBuilder } from '@rocicorp/zero'
6
6
 
7
+ // when true, serverWhere bypasses the client no-op so nested serverWhere
8
+ // calls inside permission builders actually evaluate on the client
9
+ let _evaluatingPermission = false
10
+
11
+ export function setEvaluatingPermission(value: boolean) {
12
+ _evaluatingPermission = value
13
+ }
14
+
7
15
  export function where<Table extends TableName, Builder extends Where<Table>>(
8
16
  tableName: Table,
9
17
  builder: Builder,
@@ -25,7 +33,7 @@ export function where<Table extends TableName, Builder extends Where<Table>>(
25
33
  a: ExpressionBuilder<any, any>,
26
34
  b = getQueryOrMutatorAuthData()
27
35
  ) => {
28
- if (!isServer && isServerOnly) {
36
+ if (!isServer && isServerOnly && !_evaluatingPermission) {
29
37
  // on client (web or native) where conditions always pass
30
38
  return a.and()
31
39
  }
@@ -6,10 +6,12 @@ type PreloadOptions = {
6
6
  ttl?: 'always' | 'never' | number | undefined;
7
7
  };
8
8
  export type GroupedQueries = Record<string, Record<string, (...args: any[]) => any>>;
9
- export declare function createZeroClient<Schema extends ZeroSchema, Models extends GenericModels>({ schema, models, groupedQueries, }: {
9
+ export type PermissionStrategy = 'optimistic' | 'optimistic-deny' | 'optimistic-allow';
10
+ export declare function createZeroClient<Schema extends ZeroSchema, Models extends GenericModels>({ schema, models, groupedQueries, permissionStrategy, }: {
10
11
  schema: Schema;
11
12
  models: Models;
12
13
  groupedQueries: GroupedQueries;
14
+ permissionStrategy?: PermissionStrategy;
13
15
  }): {
14
16
  zeroEvents: import("@take-out/helpers").Emitter<ZeroEvent | null>;
15
17
  ProvideZero: ({ children, authData: authDataIn, disable, ...props }: Omit<ZeroOptions<Schema, GetZeroMutators<Models>>, "schema" | "mutators"> & {
@@ -1 +1 @@
1
- {"version":3,"file":"createZeroClient.d.ts","sourceRoot":"","sources":["../src/createZeroClient.tsx"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAQd,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAOhE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAClF,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEzF,KAAK,cAAc,GAAG;IAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AAEvE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;AAEpF,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,UAAU,EACzB,MAAM,SAAS,aAAa,EAC5B,EACA,MAAM,EACN,MAAM,EACN,cAAc,GACf,EAAE;IACD,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,cAAc,CAAA;CAC/B;;yEAsII,IAAI,CAAC,WAAW,CAAC,MAAM,0BAAe,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG;QAClE,QAAQ,EAAE,SAAS,CAAA;QACnB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;QAC1B,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB;;2BAlCQ,yJAAY,CAAC,MAAM,GAAG,EAAE,CAAC,WACvB,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,yCAG9C,OAAO,GAAG,IAAI;;;SAwHA,IAAI,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,MACxE,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,UAC9C,IAAI,YACF,cAAc,GACvB;YAAE,OAAO,EAAE,MAAM,IAAI,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE;SAClC,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,MAClE,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,YAC5C,cAAc,GACvB;YAAE,OAAO,EAAE,MAAM,IAAI,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE;;EAuBpD"}
1
+ {"version":3,"file":"createZeroClient.d.ts","sourceRoot":"","sources":["../src/createZeroClient.tsx"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAQd,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAOhE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAClF,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEzF,KAAK,cAAc,GAAG;IAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AAEvE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;AAMpF,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,iBAAiB,GAAG,kBAAkB,CAAA;AAEtF,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,UAAU,EACzB,MAAM,SAAS,aAAa,EAC5B,EACA,MAAM,EACN,MAAM,EACN,cAAc,EACd,kBAAiC,GAClC,EAAE;IACD,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,cAAc,CAAA;IAC9B,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;CACxC;;yEAyKI,IAAI,CAAC,WAAW,CAAC,MAAM,0BAAe,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG;QAClE,QAAQ,EAAE,SAAS,CAAA;QACnB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;QAC1B,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB;;2BApCQ,yJAAY,CAAC,MAAM,GAAG,EAAE,CAAC,WACvB,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,yCAG9C,OAAO,GAAG,IAAI;;;SA0HA,IAAI,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,MACxE,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,UAC9C,IAAI,YACF,cAAc,GACvB;YAAE,OAAO,EAAE,MAAM,IAAI,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE;SAClC,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,MAClE,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,YAC5C,cAAc,GACvB;YAAE,OAAO,EAAE,MAAM,IAAI,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE;;EAuBpD"}
@@ -1,4 +1,5 @@
1
1
  import { Where } from './types';
2
2
  export declare function setMutationsPermissions(tableName: string, permissions: Where): void;
3
3
  export declare function getMutationsPermissions(tableName: string): Where | undefined;
4
+ export declare function getAllMutationsPermissions(): Map<string, Where>;
4
5
  //# sourceMappingURL=modelRegistry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"modelRegistry.d.ts","sourceRoot":"","sources":["../src/modelRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAI/B,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,QAE5E;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAE5E"}
1
+ {"version":3,"file":"modelRegistry.d.ts","sourceRoot":"","sources":["../src/modelRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAI/B,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,QAE5E;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAE5E;AAED,wBAAgB,0BAA0B,IAAI,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAE/D"}
package/types/where.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { TableName, Where } from './types';
2
2
  import type { Condition } from '@rocicorp/zero';
3
+ export declare function setEvaluatingPermission(value: boolean): void;
3
4
  export declare function where<Table extends TableName, Builder extends Where<Table>>(tableName: Table, builder: Builder, isServerOnly?: boolean): Where<Table, Condition>;
4
5
  export declare function where<Table extends TableName, Builder extends Where = Where<Table>>(builder: Builder): Where<Table, Condition>;
5
6
  export declare function getWhereTableName(where: Where): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"where.d.ts","sourceRoot":"","sources":["../src/where.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,gBAAgB,CAAA;AAElE,wBAAgB,KAAK,CAAC,KAAK,SAAS,SAAS,EAAE,OAAO,SAAS,KAAK,CAAC,KAAK,CAAC,EACzE,SAAS,EAAE,KAAK,EAChB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,OAAO,GACrB,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AAE1B,wBAAgB,KAAK,CAAC,KAAK,SAAS,SAAS,EAAE,OAAO,SAAS,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,EACjF,OAAO,EAAE,OAAO,GACf,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AA4C1B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,sBAE7C;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAE3D"}
1
+ {"version":3,"file":"where.d.ts","sourceRoot":"","sources":["../src/where.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,gBAAgB,CAAA;AAMlE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,QAErD;AAED,wBAAgB,KAAK,CAAC,KAAK,SAAS,SAAS,EAAE,OAAO,SAAS,KAAK,CAAC,KAAK,CAAC,EACzE,SAAS,EAAE,KAAK,EAChB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,OAAO,GACrB,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AAE1B,wBAAgB,KAAK,CAAC,KAAK,SAAS,SAAS,EAAE,OAAO,SAAS,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,EACjF,OAAO,EAAE,OAAO,GACf,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AA4C1B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,sBAE7C;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAE3D"}