@noy-db/hub 0.2.0-pre.15 → 0.2.0-pre.16

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 (197) hide show
  1. package/dist/aggregate/index.cjs +106 -10
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +1 -1
  6. package/dist/attestation/index.cjs.map +1 -1
  7. package/dist/attestation/index.d.cts +3 -3
  8. package/dist/attestation/index.d.ts +3 -3
  9. package/dist/attestation/index.js +4 -4
  10. package/dist/blobs/index.cjs.map +1 -1
  11. package/dist/blobs/index.d.cts +4 -4
  12. package/dist/blobs/index.d.ts +4 -4
  13. package/dist/blobs/index.js +3 -3
  14. package/dist/bundle/index.cjs +181 -46
  15. package/dist/bundle/index.cjs.map +1 -1
  16. package/dist/bundle/index.d.cts +5 -5
  17. package/dist/bundle/index.d.ts +5 -5
  18. package/dist/bundle/index.js +7 -7
  19. package/dist/{chunk-BIYRQQV6.js → chunk-3YWP3WBP.js} +3 -3
  20. package/dist/{chunk-VU7SWWT5.js → chunk-42FEUPZQ.js} +10 -6
  21. package/dist/chunk-42FEUPZQ.js.map +1 -0
  22. package/dist/{chunk-7EFFHEN5.js → chunk-667MB6AH.js} +118 -47
  23. package/dist/chunk-667MB6AH.js.map +1 -0
  24. package/dist/{chunk-A5ZOOZFB.js → chunk-6H2ZUNR7.js} +2 -2
  25. package/dist/{chunk-7HT2MEZ5.js → chunk-7BQ4QWYX.js} +3 -3
  26. package/dist/{chunk-DQU36Q7I.js → chunk-7Z7KSVA5.js} +13 -4
  27. package/dist/chunk-7Z7KSVA5.js.map +1 -0
  28. package/dist/{chunk-WBAYSNUQ.js → chunk-BI6ETQPF.js} +2 -2
  29. package/dist/{chunk-56DJ7JVK.js → chunk-BR3AMFGS.js} +2 -2
  30. package/dist/{chunk-COFPAMX6.js → chunk-DLZ2ONOD.js} +3 -3
  31. package/dist/{chunk-EYVQHAGH.js → chunk-DUREQF5W.js} +2 -2
  32. package/dist/{chunk-PE4AQGFH.js → chunk-E2CDVKMH.js} +3 -3
  33. package/dist/{chunk-GC4V7RU7.js → chunk-F3BPIPLS.js} +1 -1
  34. package/dist/{chunk-GC4V7RU7.js.map → chunk-F3BPIPLS.js.map} +1 -1
  35. package/dist/{chunk-L2FE64BU.js → chunk-FFXM3ZIF.js} +2 -2
  36. package/dist/{chunk-5LQG6ZO2.js → chunk-G4SCICH5.js} +8 -3
  37. package/dist/chunk-G4SCICH5.js.map +1 -0
  38. package/dist/{chunk-WGHU7BLI.js → chunk-GNI5STXQ.js} +2 -2
  39. package/dist/{chunk-C5T5AFWN.js → chunk-HBXJ37ZY.js} +11 -5
  40. package/dist/chunk-HBXJ37ZY.js.map +1 -0
  41. package/dist/{chunk-7PS7EOCF.js → chunk-IXBIFDEW.js} +2 -2
  42. package/dist/{chunk-LX3CB26H.js → chunk-KABJXG2F.js} +2 -2
  43. package/dist/{chunk-3EWXMOK3.js → chunk-L2BNJ6HM.js} +26 -11
  44. package/dist/chunk-L2BNJ6HM.js.map +1 -0
  45. package/dist/{chunk-DKO2QFSA.js → chunk-OB2ZJQ2D.js} +2 -2
  46. package/dist/{chunk-KIP6JLTF.js → chunk-OMAMZKKD.js} +2 -2
  47. package/dist/{chunk-EGD5DXFT.js → chunk-OQSRJG6A.js} +13 -1
  48. package/dist/chunk-OQSRJG6A.js.map +1 -0
  49. package/dist/{chunk-KI6HAJWL.js → chunk-QSUK7YWK.js} +2 -2
  50. package/dist/{chunk-YHPM5D7Y.js → chunk-QVIEAYTP.js} +61 -2
  51. package/dist/chunk-QVIEAYTP.js.map +1 -0
  52. package/dist/{chunk-NSCVNK5K.js → chunk-SCJPI4Z5.js} +3 -3
  53. package/dist/{chunk-NU6Q3FOR.js → chunk-TKIY625R.js} +11 -1
  54. package/dist/{chunk-NU6Q3FOR.js.map → chunk-TKIY625R.js.map} +1 -1
  55. package/dist/{chunk-OHVFWCJP.js → chunk-VLMPU56Q.js} +48 -18
  56. package/dist/chunk-VLMPU56Q.js.map +1 -0
  57. package/dist/{chunk-6AJBSQU4.js → chunk-XL35NSEN.js} +2 -2
  58. package/dist/consent/index.d.cts +4 -4
  59. package/dist/consent/index.d.ts +4 -4
  60. package/dist/derivations/index.cjs +24 -3
  61. package/dist/derivations/index.cjs.map +1 -1
  62. package/dist/derivations/index.d.cts +5 -5
  63. package/dist/derivations/index.d.ts +5 -5
  64. package/dist/derivations/index.js +2 -2
  65. package/dist/{dev-unlock-nVkuRLLe.d.cts → dev-unlock-8XzcD2Z4.d.cts} +1 -1
  66. package/dist/{dev-unlock-iAS8z9jc.d.ts → dev-unlock-DR3upLd1.d.ts} +1 -1
  67. package/dist/{executor-HSSRXDOB.js → executor-AZLS3KBK.js} +4 -4
  68. package/dist/{fanout-sidecar-N6OJX6QR.js → fanout-sidecar-67CMI3UT.js} +2 -2
  69. package/dist/guards/index.cjs +9 -5
  70. package/dist/guards/index.cjs.map +1 -1
  71. package/dist/guards/index.d.cts +5 -5
  72. package/dist/guards/index.d.ts +5 -5
  73. package/dist/guards/index.js +1 -1
  74. package/dist/{hash-DHOnRarj.d.ts → hash-CDjye9KV.d.ts} +1 -1
  75. package/dist/{hash-Cv6byZs7.d.cts → hash-DuQ88_5W.d.cts} +1 -1
  76. package/dist/history/index.cjs.map +1 -1
  77. package/dist/history/index.d.cts +5 -5
  78. package/dist/history/index.d.ts +5 -5
  79. package/dist/history/index.js +2 -2
  80. package/dist/i18n/index.cjs.map +1 -1
  81. package/dist/i18n/index.d.cts +4 -4
  82. package/dist/i18n/index.d.ts +4 -4
  83. package/dist/i18n/index.js +3 -3
  84. package/dist/{immutable-guard-yBEOYmif.d.cts → immutable-guard-CRPvu24K.d.cts} +16 -1
  85. package/dist/{immutable-guard-BehB1YGB.d.ts → immutable-guard-Dov3WvwF.d.ts} +16 -1
  86. package/dist/{index-D95VK1Qy.d.cts → index-C8Bk3-VF.d.cts} +1 -1
  87. package/dist/{index-XNB2r6bX.d.ts → index-nP99bXLg.d.ts} +1 -1
  88. package/dist/index.cjs +273 -52
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +13 -12
  91. package/dist/index.d.ts +13 -12
  92. package/dist/index.js +27 -25
  93. package/dist/index.js.map +1 -1
  94. package/dist/{issue-ADVS4OVP.js → issue-RZP3VI6O.js} +4 -4
  95. package/dist/{ledger-CWSE3BLF.js → ledger-A3LL253R.js} +3 -3
  96. package/dist/materialized-views/index.cjs +407 -5
  97. package/dist/materialized-views/index.cjs.map +1 -1
  98. package/dist/materialized-views/index.d.cts +5 -5
  99. package/dist/materialized-views/index.d.ts +5 -5
  100. package/dist/materialized-views/index.js +5 -5
  101. package/dist/noydb-WCMY2ZOW.js +35 -0
  102. package/dist/overlay-views/index.cjs +47 -17
  103. package/dist/overlay-views/index.cjs.map +1 -1
  104. package/dist/overlay-views/index.d.cts +26 -8
  105. package/dist/overlay-views/index.d.ts +26 -8
  106. package/dist/overlay-views/index.js +1 -1
  107. package/dist/periods/index.cjs.map +1 -1
  108. package/dist/periods/index.d.cts +4 -4
  109. package/dist/periods/index.d.ts +4 -4
  110. package/dist/periods/index.js +3 -3
  111. package/dist/{public-envelope-SYHEYQ3X.js → public-envelope-YP2UWMLG.js} +3 -3
  112. package/dist/query/index.cjs +24 -10
  113. package/dist/query/index.cjs.map +1 -1
  114. package/dist/query/index.d.cts +2 -2
  115. package/dist/query/index.d.ts +2 -2
  116. package/dist/query/index.js +2 -2
  117. package/dist/{registry-XGLNADIE.js → registry-EB6SISTA.js} +2 -2
  118. package/dist/{registry-DK5YWAAA.js → registry-UTA4CLQS.js} +2 -2
  119. package/dist/{revoke-ZDFKMR5E.js → revoke-HNMQZSCL.js} +4 -4
  120. package/dist/session/index.d.cts +5 -5
  121. package/dist/session/index.d.ts +5 -5
  122. package/dist/shadow/index.d.cts +4 -4
  123. package/dist/shadow/index.d.ts +4 -4
  124. package/dist/{signer-P5D7Y72U.js → signer-DCMNKXSF.js} +3 -3
  125. package/dist/snapshots/index.d.cts +4 -4
  126. package/dist/snapshots/index.d.ts +4 -4
  127. package/dist/snapshots/index.js +3 -3
  128. package/dist/{stale-JH67FU57.js → stale-W5PQTRYH.js} +2 -2
  129. package/dist/store/index.d.cts +4 -4
  130. package/dist/store/index.d.ts +4 -4
  131. package/dist/{strategy-CbneC7bS.d.ts → strategy-BtW8fAjz.d.cts} +1 -1
  132. package/dist/{strategy-CbneC7bS.d.cts → strategy-BtW8fAjz.d.ts} +1 -1
  133. package/dist/sync/index.cjs.map +1 -1
  134. package/dist/sync/index.d.cts +3 -3
  135. package/dist/sync/index.d.ts +3 -3
  136. package/dist/sync/index.js +2 -2
  137. package/dist/team/index.cjs.map +1 -1
  138. package/dist/team/index.d.cts +4 -4
  139. package/dist/team/index.d.ts +4 -4
  140. package/dist/team/index.js +5 -5
  141. package/dist/tx/index.cjs +66 -3
  142. package/dist/tx/index.cjs.map +1 -1
  143. package/dist/tx/index.d.cts +22 -6
  144. package/dist/tx/index.d.ts +22 -6
  145. package/dist/tx/index.js +7 -3
  146. package/dist/tx/index.js.map +1 -1
  147. package/dist/{types-BpPV5uyy.d.cts → types-Bze6vkwm.d.cts} +371 -139
  148. package/dist/{types-4t1-tWS4.d.ts → types-DrmBTscX.d.ts} +371 -139
  149. package/dist/{ulid-DAfenvFd.d.ts → ulid-DbBVrNSt.d.ts} +1 -1
  150. package/dist/{ulid-CiPrpGqm.d.cts → ulid-DfZlAh0u.d.cts} +1 -1
  151. package/dist/{vault-group-KOM7QRJG.js → vault-group-DX2HFQMX.js} +2 -2
  152. package/dist/{with-derivation-DBqJB3dQ.d.cts → with-derivation-CCqAchD5.d.cts} +1 -1
  153. package/dist/{with-derivation-OK9M2sJE.d.ts → with-derivation-_lySGdlm.d.ts} +1 -1
  154. package/dist/{with-materialized-view-NzuxYPDF.d.cts → with-materialized-view--4PsvMDu.d.cts} +1 -1
  155. package/dist/{with-materialized-view-Dt-ufPWQ.d.ts → with-materialized-view-QT1Tp7NO.d.ts} +1 -1
  156. package/dist/{with-overlayed-view-eDvMs6LO.d.ts → with-overlayed-view-BEXfpzSb.d.ts} +1 -1
  157. package/dist/{with-overlayed-view-CC0_ocy-.d.cts → with-overlayed-view-DlH5qmeB.d.cts} +1 -1
  158. package/package.json +3 -3
  159. package/dist/chunk-3EWXMOK3.js.map +0 -1
  160. package/dist/chunk-5LQG6ZO2.js.map +0 -1
  161. package/dist/chunk-7EFFHEN5.js.map +0 -1
  162. package/dist/chunk-C5T5AFWN.js.map +0 -1
  163. package/dist/chunk-DQU36Q7I.js.map +0 -1
  164. package/dist/chunk-EGD5DXFT.js.map +0 -1
  165. package/dist/chunk-OHVFWCJP.js.map +0 -1
  166. package/dist/chunk-VU7SWWT5.js.map +0 -1
  167. package/dist/chunk-YHPM5D7Y.js.map +0 -1
  168. package/dist/noydb-GZGFBA4E.js +0 -35
  169. /package/dist/{chunk-BIYRQQV6.js.map → chunk-3YWP3WBP.js.map} +0 -0
  170. /package/dist/{chunk-A5ZOOZFB.js.map → chunk-6H2ZUNR7.js.map} +0 -0
  171. /package/dist/{chunk-7HT2MEZ5.js.map → chunk-7BQ4QWYX.js.map} +0 -0
  172. /package/dist/{chunk-WBAYSNUQ.js.map → chunk-BI6ETQPF.js.map} +0 -0
  173. /package/dist/{chunk-56DJ7JVK.js.map → chunk-BR3AMFGS.js.map} +0 -0
  174. /package/dist/{chunk-COFPAMX6.js.map → chunk-DLZ2ONOD.js.map} +0 -0
  175. /package/dist/{chunk-EYVQHAGH.js.map → chunk-DUREQF5W.js.map} +0 -0
  176. /package/dist/{chunk-PE4AQGFH.js.map → chunk-E2CDVKMH.js.map} +0 -0
  177. /package/dist/{chunk-L2FE64BU.js.map → chunk-FFXM3ZIF.js.map} +0 -0
  178. /package/dist/{chunk-WGHU7BLI.js.map → chunk-GNI5STXQ.js.map} +0 -0
  179. /package/dist/{chunk-7PS7EOCF.js.map → chunk-IXBIFDEW.js.map} +0 -0
  180. /package/dist/{chunk-LX3CB26H.js.map → chunk-KABJXG2F.js.map} +0 -0
  181. /package/dist/{chunk-DKO2QFSA.js.map → chunk-OB2ZJQ2D.js.map} +0 -0
  182. /package/dist/{chunk-KIP6JLTF.js.map → chunk-OMAMZKKD.js.map} +0 -0
  183. /package/dist/{chunk-KI6HAJWL.js.map → chunk-QSUK7YWK.js.map} +0 -0
  184. /package/dist/{chunk-NSCVNK5K.js.map → chunk-SCJPI4Z5.js.map} +0 -0
  185. /package/dist/{chunk-6AJBSQU4.js.map → chunk-XL35NSEN.js.map} +0 -0
  186. /package/dist/{executor-HSSRXDOB.js.map → executor-AZLS3KBK.js.map} +0 -0
  187. /package/dist/{fanout-sidecar-N6OJX6QR.js.map → fanout-sidecar-67CMI3UT.js.map} +0 -0
  188. /package/dist/{issue-ADVS4OVP.js.map → issue-RZP3VI6O.js.map} +0 -0
  189. /package/dist/{ledger-CWSE3BLF.js.map → ledger-A3LL253R.js.map} +0 -0
  190. /package/dist/{noydb-GZGFBA4E.js.map → noydb-WCMY2ZOW.js.map} +0 -0
  191. /package/dist/{public-envelope-SYHEYQ3X.js.map → public-envelope-YP2UWMLG.js.map} +0 -0
  192. /package/dist/{registry-DK5YWAAA.js.map → registry-EB6SISTA.js.map} +0 -0
  193. /package/dist/{registry-XGLNADIE.js.map → registry-UTA4CLQS.js.map} +0 -0
  194. /package/dist/{revoke-ZDFKMR5E.js.map → revoke-HNMQZSCL.js.map} +0 -0
  195. /package/dist/{signer-P5D7Y72U.js.map → signer-DCMNKXSF.js.map} +0 -0
  196. /package/dist/{stale-JH67FU57.js.map → stale-W5PQTRYH.js.map} +0 -0
  197. /package/dist/{vault-group-KOM7QRJG.js.map → vault-group-DX2HFQMX.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -2879,13 +2879,22 @@ var init_predicate = __esm({
2879
2879
  });
2880
2880
 
2881
2881
  // src/money/money-reducer.ts
2882
- function toScaledInt(v) {
2883
- if (typeof v === "string" || typeof v === "number" || typeof v === "bigint") {
2884
- try {
2885
- return BigInt(v);
2886
- } catch {
2887
- return null;
2882
+ function toScaledIntFromAny(v, scale) {
2883
+ if (typeof v === "bigint") return v;
2884
+ if (typeof v === "number") {
2885
+ const r = parseToScaledInt(v, scale);
2886
+ return r.ok ? r.value : null;
2887
+ }
2888
+ if (typeof v === "string") {
2889
+ if (!v.includes(".")) {
2890
+ try {
2891
+ return BigInt(v);
2892
+ } catch {
2893
+ return null;
2894
+ }
2888
2895
  }
2896
+ const r = parseToScaledInt(v, scale);
2897
+ return r.ok ? r.value : null;
2889
2898
  }
2890
2899
  return null;
2891
2900
  }
@@ -2893,13 +2902,15 @@ function readMoney(record, field, desc) {
2893
2902
  const raw = readPath(record, field);
2894
2903
  if (raw === null || raw === void 0) return null;
2895
2904
  if (desc.mode === "fixed") {
2896
- const value2 = toScaledInt(raw);
2897
- return value2 === null ? null : { currency: desc.fixedCurrency, value: value2 };
2905
+ const cur = desc.fixedCurrency;
2906
+ const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
2907
+ return value2 === null ? null : { currency: cur, value: value2 };
2898
2908
  }
2899
2909
  if (typeof raw !== "object") return null;
2900
2910
  const o = raw;
2901
2911
  if (typeof o.currency !== "string") return null;
2902
- const value = toScaledInt(o.amount);
2912
+ const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
2913
+ const value = toScaledIntFromAny(o.amount, scale);
2903
2914
  return value === null ? null : { currency: o.currency, value };
2904
2915
  }
2905
2916
  function targetScaleFor(desc, currency) {
@@ -3217,11 +3228,14 @@ function warnCardinalityApproaching(fields, observed) {
3217
3228
  `[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ${Math.round(observed / GROUPBY_MAX_CARDINALITY * 100)}% of the ${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with .where() before grouping, or switch to a lower-cardinality field.`
3218
3229
  );
3219
3230
  }
3220
- function groupAndReduce(records, fieldOrFields, spec) {
3231
+ function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
3221
3232
  const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
3222
3233
  if (fields.length === 0) {
3223
3234
  throw new Error(".groupBy() requires at least one field");
3224
3235
  }
3236
+ if (moneyFields) {
3237
+ spec = wrapMoneyReducers(spec, moneyFields);
3238
+ }
3225
3239
  const buckets = /* @__PURE__ */ new Map();
3226
3240
  const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
3227
3241
  for (const record of records) {
@@ -3664,10 +3678,14 @@ function summarizeQueryPlan(query) {
3664
3678
  });
3665
3679
  }
3666
3680
  function summarizeUnionPlan(spec) {
3667
- const arms = (spec.unionSources ?? []).map((s) => s.collection).join(",");
3681
+ const arms = (spec.unionSources ?? []).map((s) => {
3682
+ const joins = s.join?.length ? `[${s.join.map((j) => `${j.field}\u2192${j.as}`).join(";")}]` : "";
3683
+ return `${s.collection}${joins}`;
3684
+ }).join(",");
3668
3685
  const groupBy = Array.isArray(spec.groupBy) ? [...spec.groupBy].sort().join(",") : typeof spec.groupBy === "string" ? spec.groupBy : "";
3669
3686
  const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(",") : "";
3670
- return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})`;
3687
+ const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(",") : "";
3688
+ return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})`;
3671
3689
  }
3672
3690
  var init_dependency_analyzer = __esm({
3673
3691
  "src/materialized-views/dependency-analyzer.ts"() {
@@ -3794,6 +3812,7 @@ var init_registry = __esm({
3794
3812
  let isQuery = false;
3795
3813
  if (spec.unionSources) {
3796
3814
  dependencies = new Set(spec.unionSources.map((s) => s.collection));
3815
+ if (spec.sources) for (const s of spec.sources) dependencies.add(s);
3797
3816
  queryPlanSummary = summarizeUnionPlan(spec);
3798
3817
  } else {
3799
3818
  const q = spec.query(dbForQuery);
@@ -3948,7 +3967,13 @@ async function materializeUnionResult(spec, db) {
3948
3967
  const unified = [];
3949
3968
  for (const arm of spec.unionSources) {
3950
3969
  const coll = db.collection(arm.collection);
3951
- const sourceRows = coll.query().toArray();
3970
+ let q = coll.query();
3971
+ if (arm.join?.length) {
3972
+ for (const leg of arm.join) {
3973
+ q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy });
3974
+ }
3975
+ }
3976
+ const sourceRows = q.toArray();
3952
3977
  for (const r of sourceRows) {
3953
3978
  const mapped = arm.map(r);
3954
3979
  if (mapped == null) continue;
@@ -3965,7 +3990,7 @@ async function materializeUnionResult(spec, db) {
3965
3990
  }
3966
3991
  return [...seen.values()];
3967
3992
  }
3968
- return groupAndReduce(unified, groupFields, spec.aggregate);
3993
+ return groupAndReduce(unified, groupFields, spec.aggregate, spec.moneyFields);
3969
3994
  }
3970
3995
  async function listOutputIds(outputColl) {
3971
3996
  const cAny = outputColl;
@@ -4520,11 +4545,15 @@ var init_read_only_facade = __esm({
4520
4545
  });
4521
4546
 
4522
4547
  // src/derivations/strategy-hash.ts
4523
- async function computeStrategyHash(source, outputKeys, derive) {
4548
+ async function computeStrategyHash(source, outputKeys, derive, sources) {
4524
4549
  const canonical2 = JSON.stringify({
4525
4550
  source,
4526
4551
  outputs: [...outputKeys].sort(),
4527
- derive: derive.toString()
4552
+ derive: derive.toString(),
4553
+ // Declared sibling sources (#344) — adding/removing a trigger
4554
+ // collection invalidates cached derived records. Omitted when empty
4555
+ // so strategies without siblings keep their existing hash.
4556
+ ...sources?.length ? { sources: [...sources].sort() } : {}
4528
4557
  });
4529
4558
  const bytes = new TextEncoder().encode(canonical2);
4530
4559
  const digest = await crypto.subtle.digest("SHA-256", bytes);
@@ -4553,11 +4582,16 @@ var init_registry3 = __esm({
4553
4582
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4554
4583
  async register(spec) {
4555
4584
  const outputKeys = Object.keys(spec.outputs);
4556
- const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive);
4585
+ const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources);
4557
4586
  const reg = { spec, strategyHash };
4558
4587
  const fromSource = this._bySource.get(spec.source);
4559
4588
  if (fromSource) fromSource.push(reg);
4560
4589
  else this._bySource.set(spec.source, [reg]);
4590
+ for (const extra of spec.sources ?? []) {
4591
+ const fromExtra = this._bySource.get(extra);
4592
+ if (fromExtra) fromExtra.push(reg);
4593
+ else this._bySource.set(extra, [reg]);
4594
+ }
4561
4595
  for (const key of outputKeys) {
4562
4596
  const output = spec.outputs[key];
4563
4597
  if (!output) continue;
@@ -5892,6 +5926,7 @@ __export(src_exports, {
5892
5926
  resolveI18nText: () => resolveI18nText,
5893
5927
  resolvePolicy: () => resolvePolicy,
5894
5928
  resolvePublicEnvelopeSchema: () => resolveSchema,
5929
+ resolveSequenceKey: () => resolveSequenceKey,
5895
5930
  resolveSession: () => resolveSession,
5896
5931
  revokeAllSessions: () => revokeAllSessions,
5897
5932
  revokeDelegation: () => revokeDelegation,
@@ -7990,6 +8025,21 @@ init_crypto();
7990
8025
  init_errors();
7991
8026
  var SEQUENCE_COLLECTION = "_sequences";
7992
8027
  var MAX_NEXT_ATTEMPTS = 16;
8028
+ function resolveSequenceKey(series, opts) {
8029
+ const partition = opts?.partition;
8030
+ if (!partition || partition.length === 0) return series;
8031
+ const parts = partition.map((p) => {
8032
+ if (typeof p === "number" && !Number.isFinite(p)) {
8033
+ throw new ValidationError(`sequence partition component must be a finite number, got ${p}`);
8034
+ }
8035
+ const s = String(p);
8036
+ if (s === "") {
8037
+ throw new ValidationError("sequence partition component must not be empty");
8038
+ }
8039
+ return encodeURIComponent(s);
8040
+ });
8041
+ return `${series}\0${parts.join("/")}`;
8042
+ }
7993
8043
  async function sleepBackoff(attempt) {
7994
8044
  const ceil = Math.min(2 ** attempt, 32);
7995
8045
  const ms = Math.floor(Math.random() * ceil);
@@ -8020,7 +8070,8 @@ var SequenceStore = class {
8020
8070
  handle(name) {
8021
8071
  return {
8022
8072
  next: () => this.next(name),
8023
- peek: () => this.peek(name)
8073
+ peek: () => this.peek(name),
8074
+ seedTo: (n) => this.seedTo(name, n)
8024
8075
  };
8025
8076
  }
8026
8077
  assertOnline() {
@@ -8073,6 +8124,30 @@ var SequenceStore = class {
8073
8124
  void lastConflict;
8074
8125
  throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
8075
8126
  }
8127
+ async seedTo(name, n) {
8128
+ this.assertOnline();
8129
+ if (n <= 0) return;
8130
+ let lastConflict;
8131
+ for (let attempt = 0; attempt < MAX_NEXT_ATTEMPTS; attempt++) {
8132
+ const { env, value } = await this.read(name);
8133
+ if (value >= n) return;
8134
+ const expectedVersion = env?._v ?? 0;
8135
+ const envelope = await this.encryptState({ value: n }, expectedVersion + 1);
8136
+ try {
8137
+ await this.adapter.put(this.vault, SEQUENCE_COLLECTION, name, envelope, expectedVersion);
8138
+ return;
8139
+ } catch (err) {
8140
+ if (err instanceof ConflictError) {
8141
+ lastConflict = err;
8142
+ if (attempt < MAX_NEXT_ATTEMPTS - 1) await sleepBackoff(attempt);
8143
+ continue;
8144
+ }
8145
+ throw err;
8146
+ }
8147
+ }
8148
+ void lastConflict;
8149
+ throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
8150
+ }
8076
8151
  };
8077
8152
 
8078
8153
  // src/numbering/descriptor.ts
@@ -14599,7 +14674,7 @@ var TxCollection = class {
14599
14674
  this._ctx._ops.push(op);
14600
14675
  }
14601
14676
  };
14602
- async function runTransaction(db, fn, options) {
14677
+ async function runTransaction(db, fn, options, txInvariants) {
14603
14678
  if (options?.amendment) {
14604
14679
  if (typeof options.reason !== "string" || options.reason.trim().length === 0) {
14605
14680
  throw new ValidationError(
@@ -14623,12 +14698,19 @@ async function runTransaction(db, fn, options) {
14623
14698
  }
14624
14699
  const priorEnvelopes = /* @__PURE__ */ new Map();
14625
14700
  const store = db._store;
14701
+ const invariants = txInvariants ?? [];
14702
+ const watchedScopes = new Set(invariants.map((i) => i.scope));
14703
+ const plainBefore = /* @__PURE__ */ new Map();
14626
14704
  for (const op of ctx._ops) {
14627
14705
  const key = keyOf(op);
14628
14706
  if (!priorEnvelopes.has(key)) {
14629
14707
  const env = await store.get(op.vaultName, op.collectionName, op.id);
14630
14708
  priorEnvelopes.set(key, env);
14631
14709
  }
14710
+ if (watchedScopes.has(op.collectionName) && !plainBefore.has(key)) {
14711
+ const prior = await db.vault(op.vaultName).collection(op.collectionName).get(op.id);
14712
+ plainBefore.set(key, prior ?? null);
14713
+ }
14632
14714
  if (op.expectedVersion !== void 0) {
14633
14715
  const env = priorEnvelopes.get(key) ?? null;
14634
14716
  const actual = env?._v ?? 0;
@@ -14726,6 +14808,58 @@ async function runTransaction(db, fn, options) {
14726
14808
  );
14727
14809
  }
14728
14810
  }
14811
+ if (invariants.length > 0) {
14812
+ const lastOp = /* @__PURE__ */ new Map();
14813
+ const order = [];
14814
+ for (const op of ctx._ops) {
14815
+ const key = keyOf(op);
14816
+ if (!lastOp.has(key)) order.push(key);
14817
+ lastOp.set(key, op);
14818
+ }
14819
+ const changesByScope = /* @__PURE__ */ new Map();
14820
+ const scopeVault = /* @__PURE__ */ new Map();
14821
+ for (const key of order) {
14822
+ const op = lastOp.get(key);
14823
+ if (!watchedScopes.has(op.collectionName)) continue;
14824
+ const before = plainBefore.get(key) ?? null;
14825
+ const after = op.type === "delete" ? null : op.record ?? null;
14826
+ const change = { before, after };
14827
+ const arr = changesByScope.get(op.collectionName);
14828
+ if (arr) arr.push(change);
14829
+ else changesByScope.set(op.collectionName, [change]);
14830
+ scopeVault.set(op.collectionName, op.vaultName);
14831
+ }
14832
+ try {
14833
+ for (const inv of invariants) {
14834
+ const changes = changesByScope.get(inv.scope);
14835
+ if (changes === void 0 || changes.length === 0) continue;
14836
+ const vaultName = scopeVault.get(inv.scope);
14837
+ const v = db.vault(vaultName);
14838
+ const facade = v._getReadOnlyFacade() ?? {
14839
+ collection(name) {
14840
+ const c = v.collection(name);
14841
+ return {
14842
+ get: (id) => c.get(id),
14843
+ list: () => c.list(),
14844
+ query: () => c.query()
14845
+ };
14846
+ }
14847
+ };
14848
+ const ctxForInv = {
14849
+ existing: null,
14850
+ vault: facade,
14851
+ userId: v.userId,
14852
+ role: v.role
14853
+ };
14854
+ await inv.check(changes, ctxForInv);
14855
+ }
14856
+ } catch (err) {
14857
+ await revertExecuted(ctx._executed, store, db);
14858
+ throw err instanceof InvariantError ? err : new InvariantError(
14859
+ err instanceof Error ? err.message : `invariant violated: ${String(err)}`
14860
+ );
14861
+ }
14862
+ }
14729
14863
  return bodyResult;
14730
14864
  }
14731
14865
  async function revertExecuted(executed, store, db) {
@@ -15731,9 +15865,18 @@ var Collection = class {
15731
15865
  if (DerivationExecutor2 === null) {
15732
15866
  ({ DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2)));
15733
15867
  }
15734
- const sourceWithId = { ...incoming, id };
15868
+ let sourceWithId;
15869
+ let sourceVersion = version;
15870
+ if (spec.source === this.name) {
15871
+ sourceWithId = { ...incoming, id };
15872
+ } else {
15873
+ const primary = await this.derivationSource.getCollection(spec.source).get(id);
15874
+ if (primary === null || primary === void 0) continue;
15875
+ sourceWithId = { ...primary, id };
15876
+ sourceVersion = 0;
15877
+ }
15735
15878
  const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
15736
- const result = await DerivationExecutor2.run(spec, sourceWithId, version, strategyHash, ctx);
15879
+ const result = await DerivationExecutor2.run(spec, sourceWithId, sourceVersion, strategyHash, ctx);
15737
15880
  for (const key of Object.keys(spec.outputs)) {
15738
15881
  const out = result.outputs[key];
15739
15882
  if (!out) continue;
@@ -17716,37 +17859,34 @@ var OverlayedCollection = class {
17716
17859
  /** Get the merged row by id. */
17717
17860
  async get(id) {
17718
17861
  const overlayRow = await this.overlayCollection.get(id);
17719
- if (overlayRow !== null && this.shadowPredicateApplies(overlayRow)) {
17720
- return overlayRow;
17721
- }
17722
17862
  const baseRow = await this.baseCollection.get(id);
17723
- if (baseRow !== null) return baseRow;
17724
- return null;
17863
+ return this.mergeRows(overlayRow, baseRow);
17725
17864
  }
17726
17865
  /** List union of base + overlay ids, applying the merge per row. */
17727
17866
  async list() {
17728
17867
  const baseRows = await this.baseCollection.list();
17729
17868
  const overlayRows = await this.overlayCollection.list();
17730
- const merged = /* @__PURE__ */ new Map();
17731
17869
  const idOf = (row) => {
17732
17870
  if (this.baseRowKey) return this.baseRowKey(row);
17733
17871
  const idField = row.id;
17734
17872
  return typeof idField === "string" ? idField : "";
17735
17873
  };
17874
+ const baseById = /* @__PURE__ */ new Map();
17875
+ const overlayById = /* @__PURE__ */ new Map();
17736
17876
  for (const row of baseRows) {
17737
17877
  const id = idOf(row);
17738
- if (id) merged.set(id, row);
17878
+ if (id) baseById.set(id, row);
17739
17879
  }
17740
17880
  for (const row of overlayRows) {
17741
17881
  const id = idOf(row);
17742
- if (!id) continue;
17743
- if (this.shadowPredicateApplies(row)) {
17744
- merged.set(id, row);
17745
- } else if (!merged.has(id)) {
17746
- continue;
17747
- }
17882
+ if (id) overlayById.set(id, row);
17883
+ }
17884
+ const out = [];
17885
+ for (const id of /* @__PURE__ */ new Set([...baseById.keys(), ...overlayById.keys()])) {
17886
+ const merged = this.mergeRows(overlayById.get(id) ?? null, baseById.get(id) ?? null);
17887
+ if (merged !== null) out.push(merged);
17748
17888
  }
17749
- return [...merged.values()];
17889
+ return out;
17750
17890
  }
17751
17891
  /**
17752
17892
  * Write to the overlay. Two forms:
@@ -17786,9 +17926,42 @@ var OverlayedCollection = class {
17786
17926
  async delete(id) {
17787
17927
  await this.overlayCollection.delete(id);
17788
17928
  }
17789
- /** True when `overlay[shadowField] === shadowValue`. */
17790
- shadowPredicateApplies(row) {
17791
- return row[this.spec.shadowField] === this.spec.shadowValue;
17929
+ /**
17930
+ * Merge a single id's overlay + base rows into the visible row.
17931
+ *
17932
+ * Priority (first match wins):
17933
+ * 1. Binary shadow win — overlay present AND
17934
+ * `overlay[shadowField] === shadowValue` → return the overlay row
17935
+ * entirely. This stays FIRST so the original binary behaviour is
17936
+ * unchanged whether or not `mergeMode` is configured.
17937
+ * 2. Field-level merge — overlay present, `mergeMode` configured,
17938
+ * and a rule whose `whenStatus` equals `overlay[shadowField]`.
17939
+ * The matched rule pulls its `overlayFields` (those present on
17940
+ * the overlay row) on top of the base row. With no base row, the
17941
+ * overlay row is returned as-is.
17942
+ * 3. Fallback — return the base row (possibly `null`). An
17943
+ * overlay-only row that qualifies under neither (1) nor (2) and
17944
+ * has no base is therefore NOT surfaced.
17945
+ */
17946
+ mergeRows(overlayRow, baseRow) {
17947
+ const shadowField = this.spec.shadowField;
17948
+ if (overlayRow !== null && overlayRow[shadowField] === this.spec.shadowValue) {
17949
+ return overlayRow;
17950
+ }
17951
+ if (overlayRow !== null && this.spec.mergeMode) {
17952
+ const status = overlayRow[shadowField];
17953
+ const rule = this.spec.mergeMode.rules.find((r) => r.whenStatus === status);
17954
+ if (rule) {
17955
+ if (baseRow === null) return overlayRow;
17956
+ const overlaySrc = overlayRow;
17957
+ const picked = {};
17958
+ for (const field of rule.overlayFields) {
17959
+ if (field in overlaySrc) picked[field] = overlaySrc[field];
17960
+ }
17961
+ return { ...baseRow, ...picked };
17962
+ }
17963
+ }
17964
+ return baseRow;
17792
17965
  }
17793
17966
  // ─── Throw-stubs for the unimplemented Collection<T> surface ───────
17794
17967
  //
@@ -20212,17 +20385,23 @@ var Vault = class {
20212
20385
  * const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
20213
20386
  * ```
20214
20387
  */
20215
- sequence(name) {
20216
- if (this.numberingConfigs.has(name)) {
20388
+ sequence(series, opts) {
20389
+ if (series.includes("\0")) {
20390
+ throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
20391
+ }
20392
+ if (this.numberingConfigs.has(series)) {
20217
20393
  const eng = this.deferred();
20218
20394
  return {
20219
- next: async (opts) => {
20220
- if (!opts?.for) {
20221
- throw new ValidationError(`sequence("${name}") is a deferred-numbering series; call next({ for: recordId }).`);
20395
+ next: async (nextOpts) => {
20396
+ if (!nextOpts?.for) {
20397
+ throw new ValidationError(`sequence("${series}") is a deferred-numbering series; call next({ for: recordId }).`);
20222
20398
  }
20223
- return (await eng.enqueue(name, opts.for)).assigned;
20399
+ return (await eng.enqueue(series, nextOpts.for)).assigned;
20224
20400
  },
20225
- peek: () => eng.peek(name)
20401
+ peek: () => eng.peek(series),
20402
+ seedTo: () => {
20403
+ throw new ValidationError(`sequence("${series}") is a deferred-numbering series; seedTo is CAS-only.`);
20404
+ }
20226
20405
  };
20227
20406
  }
20228
20407
  if (!this.sequenceStore) {
@@ -20234,7 +20413,7 @@ var Vault = class {
20234
20413
  actor: this.keyring.userId
20235
20414
  });
20236
20415
  }
20237
- return this.sequenceStore.handle(name);
20416
+ return this.sequenceStore.handle(resolveSequenceKey(series, opts));
20238
20417
  }
20239
20418
  /** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
20240
20419
  deferred() {
@@ -20526,9 +20705,24 @@ var Vault = class {
20526
20705
  });
20527
20706
  }
20528
20707
  if (rule.mode === "cascade") {
20708
+ const txCtx = this.noydb._activeTxContextOrNull;
20529
20709
  for (const match of matches) {
20530
20710
  const matchId = match["id"] ?? null;
20531
20711
  if (matchId === null) continue;
20712
+ if (txCtx !== null) {
20713
+ const prior = await this.adapter.get(this.name, rule.collection, matchId);
20714
+ if (prior !== null) {
20715
+ txCtx._executed.push({
20716
+ op: {
20717
+ type: "delete",
20718
+ vaultName: this.name,
20719
+ collectionName: rule.collection,
20720
+ id: matchId
20721
+ },
20722
+ priorEnvelope: prior
20723
+ });
20724
+ }
20725
+ }
20532
20726
  await fromCollection.delete(matchId);
20533
20727
  }
20534
20728
  }
@@ -22727,7 +22921,7 @@ var NOT_ENABLED6 = new Error(
22727
22921
  'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
22728
22922
  );
22729
22923
  var NO_TX = {
22730
- async runTransaction() {
22924
+ async runTransaction(_db, _fn, _options, _txInvariants) {
22731
22925
  throw NOT_ENABLED6;
22732
22926
  },
22733
22927
  async runDryRun() {
@@ -26045,7 +26239,7 @@ function recordId2(record) {
26045
26239
  return typeof id === "string" ? id : "";
26046
26240
  }
26047
26241
  function immutableGuard(config) {
26048
- const { collection, after, appendOnly, amendmentRoles } = config;
26242
+ const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config;
26049
26243
  if (appendOnly && after !== void 0) {
26050
26244
  throw new ValidationError("immutableGuard: `after` and `appendOnly` are mutually exclusive");
26051
26245
  }
@@ -26071,12 +26265,16 @@ function immutableGuard(config) {
26071
26265
  }
26072
26266
  },
26073
26267
  // The authorized override: inside an amendment transaction the
26074
- // check/onDelete are skipped and the change is ledgered. No extra
26075
- // invariant — the amendment itself is the sanctioned exception.
26268
+ // check/onDelete are skipped and the change is ledgered. By default
26269
+ // there is no extra invariant — the amendment itself is the
26270
+ // sanctioned exception. Callers may supply `amendmentInvariant` to
26271
+ // keep a constraint inviolable even under amendment (e.g. forbid
26272
+ // deletes, or preserve a cross-record sum); a throw reverts the
26273
+ // amendment and surfaces as `InvariantError`.
26076
26274
  amendment: {
26077
26275
  roles: amendmentRoles ?? ["admin", "owner"],
26078
- invariant: () => {
26079
- }
26276
+ invariant: amendmentInvariant ?? (() => {
26277
+ })
26080
26278
  }
26081
26279
  };
26082
26280
  return withGuard(spec);
@@ -26097,6 +26295,18 @@ function withDerivation(spec) {
26097
26295
  if (typeof spec.derive !== "function") {
26098
26296
  throw new ValidationError("withDerivation: derive must be a function");
26099
26297
  }
26298
+ if (spec.sources !== void 0) {
26299
+ for (const extra of spec.sources) {
26300
+ if (typeof extra !== "string" || extra.length === 0) {
26301
+ throw new ValidationError("withDerivation: each entry in sources[] must be a non-empty string");
26302
+ }
26303
+ if (extra === spec.source) {
26304
+ throw new ValidationError(
26305
+ `withDerivation: sources[] must not contain the primary source "${spec.source}"`
26306
+ );
26307
+ }
26308
+ }
26309
+ }
26100
26310
  const lifecycleMode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
26101
26311
  for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {
26102
26312
  if (outputSpec.shape === "array") {
@@ -26183,6 +26393,16 @@ function withMaterializedView(spec) {
26183
26393
  `withMaterializedView "${spec.name}": UNION strategy with aggregate requires groupBy \u2014 use groupBy to declare the bucketing keys, or remove aggregate for a pure dedup MV`
26184
26394
  );
26185
26395
  }
26396
+ if (spec.moneyFields && !spec.aggregate) {
26397
+ throw new MaterializedViewConfigError(
26398
+ `withMaterializedView "${spec.name}": moneyFields requires aggregate \u2014 moneyFields rewrites sum/min/max reducers over money output fields, so it is meaningless without an aggregate spec`
26399
+ );
26400
+ }
26401
+ if (spec.unionSources.some((s) => s.join && s.join.length > 0) && (!spec.sources || spec.sources.length === 0)) {
26402
+ throw new MaterializedViewConfigError(
26403
+ `withMaterializedView "${spec.name}": a unionSources arm declares join(s) but no \`sources\` are listed \u2014 declare sources: [...] with the right-side (join-target) collection names so writes to them trigger MV refresh`
26404
+ );
26405
+ }
26186
26406
  if (spec.predicates) {
26187
26407
  throw new MaterializedViewConfigError(
26188
26408
  `withMaterializedView "${spec.name}": predicates are not supported on UNION strategies \u2014 UNION mode does not use a Query<T> chain, so .wherePredicate() cannot fire. Use the query() form, or open an issue if per-arm predicates are needed`
@@ -27438,6 +27658,7 @@ function shortJSON(value) {
27438
27658
  resolveI18nText,
27439
27659
  resolvePolicy,
27440
27660
  resolvePublicEnvelopeSchema,
27661
+ resolveSequenceKey,
27441
27662
  resolveSession,
27442
27663
  revokeAllSessions,
27443
27664
  revokeDelegation,