@camstack/types 1.2.397 → 1.2.398

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 (35) hide show
  1. package/dist/addon.js +17 -2
  2. package/dist/addon.mjs +15 -3
  3. package/dist/capabilities/dishwasher.cap.d.ts +9 -0
  4. package/dist/capabilities/index.d.ts +2 -2
  5. package/dist/capabilities/notification-rules.cap.d.ts +92 -0
  6. package/dist/capabilities/osd-manager.cap.d.ts +24 -0
  7. package/dist/capabilities/records.cap.d.ts +67 -3
  8. package/dist/capabilities/storage-admin.cap.d.ts +0 -2
  9. package/dist/capabilities/user-management.cap.d.ts +2 -28
  10. package/dist/client.js +88 -9
  11. package/dist/client.mjs +88 -9
  12. package/dist/enums/event-category.d.ts +9 -0
  13. package/dist/enums.js +1 -1
  14. package/dist/enums.mjs +1 -1
  15. package/dist/{event-category-kpu1bZMs.js → event-category-7AwNbPjm.js} +9 -0
  16. package/dist/{event-category-7pZEBmVg.mjs → event-category-BfJwUd2S.mjs} +9 -0
  17. package/dist/generated/addon-api.d.ts +7 -14
  18. package/dist/generated/addon-cap-facts.d.ts +1 -1
  19. package/dist/generated/method-access-map.d.ts +1 -1
  20. package/dist/generated/system-proxy.d.ts +1 -1
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +181 -61
  23. package/dist/index.mjs +177 -60
  24. package/dist/interfaces/auth.d.ts +1 -1
  25. package/dist/interfaces/event-bus.d.ts +24 -5
  26. package/dist/notification/alert-episode.d.ts +7 -0
  27. package/dist/records/errors.d.ts +9 -1
  28. package/dist/records/index.d.ts +1 -0
  29. package/dist/records/record-kind.d.ts +62 -2
  30. package/dist/records/records-access.d.ts +36 -1
  31. package/dist/records/transact-rules.d.ts +7 -0
  32. package/dist/schemas/auth-records.d.ts +0 -12
  33. package/dist/{sleep-Bo7fq491.js → sleep-CD18m9HF.js} +478 -311
  34. package/dist/{sleep-CueztlR1.mjs → sleep-DJFUfKvA.mjs} +461 -312
  35. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- const require_event_category = require("./event-category-kpu1bZMs.js");
1
+ const require_event_category = require("./event-category-7AwNbPjm.js");
2
2
  let zod = require("zod");
3
3
  //#region src/audio/audio-chunk-format.ts
4
4
  /**
@@ -3227,7 +3227,8 @@ var RECORDS_ERROR_CODES = [
3227
3227
  "invalid-record",
3228
3228
  "declaration-refused",
3229
3229
  "no-caller",
3230
- "batch-too-large"
3230
+ "batch-too-large",
3231
+ "transact-refused"
3231
3232
  ];
3232
3233
  var PREFIX = "records/";
3233
3234
  /** The base of every records failure. `detail` is the human part of the message. */
@@ -3335,6 +3336,17 @@ var BatchTooLargeError = class extends RecordsError {
3335
3336
  this.name = "BatchTooLargeError";
3336
3337
  }
3337
3338
  };
3339
+ /**
3340
+ * A `transact` the engine refuses before touching a row: an op on a kind the
3341
+ * caller did not declare in ONE file and ONE `transactFamily`. The detail names
3342
+ * which rule (`cross-file`, `cross-family`, `no-family`); it is never a silent split.
3343
+ */
3344
+ var TransactRefusedError = class extends RecordsError {
3345
+ constructor(detail) {
3346
+ super("transact-refused", detail);
3347
+ this.name = "TransactRefusedError";
3348
+ }
3349
+ };
3338
3350
  var FACTORIES = {
3339
3351
  "rev-conflict": (d) => new RevConflictError(d),
3340
3352
  "too-large": (d) => new RecordTooLargeError(d),
@@ -3349,7 +3361,8 @@ var FACTORIES = {
3349
3361
  "invalid-record": (d) => new InvalidRecordError(d),
3350
3362
  "declaration-refused": (d) => new DeclarationRefusedError(d),
3351
3363
  "no-caller": (d) => new NoCallerError(d),
3352
- "batch-too-large": (d) => new BatchTooLargeError(d)
3364
+ "batch-too-large": (d) => new BatchTooLargeError(d),
3365
+ "transact-refused": (d) => new TransactRefusedError(d)
3353
3366
  };
3354
3367
  function isCode(value) {
3355
3368
  return RECORDS_ERROR_CODES.some((code) => code === value);
@@ -3640,8 +3653,7 @@ var EntryDescriptorSchema = zod.z.object({
3640
3653
  /** JSON Schema of the entry body; `secret: true` marks a password field. */
3641
3654
  fields: zod.z.record(zod.z.string(), zod.z.unknown())
3642
3655
  }).strict();
3643
- /** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
3644
- var RecordKindDeclarationSchema = zod.z.object({
3656
+ var RecordKindDeclarationShape = zod.z.object({
3645
3657
  database: zod.z.enum(RECORD_DATABASES).default("settings"),
3646
3658
  class: zod.z.enum(RECORD_CLASSES),
3647
3659
  writeTrigger: zod.z.string().min(1),
@@ -3659,18 +3671,18 @@ var RecordKindDeclarationSchema = zod.z.object({
3659
3671
  secretPaths: zod.z.array(zod.z.string().min(1)).default([]),
3660
3672
  /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
3661
3673
  secretArrayKeys: zod.z.record(zod.z.string(), zod.z.string()).default({}),
3662
- /**
3663
- * RETIRED (D900): the legacy table a kind adopted from during the records
3664
- * migration. Nothing declares it any more and nothing reads it; the schema
3665
- * still ACCEPTS it because the catalog rows the migration wrote carry it
3666
- * (an orphaned kind is never redeclared) and an addon built before the
3667
- * removal still sends it. Never set it.
3668
- */
3669
- adoptsFrom: zod.z.string().min(1).optional(),
3670
3674
  /** Kinds that must agree through `transact` sit in one file under one family name. */
3671
3675
  transactFamily: zod.z.string().min(1).optional(),
3672
3676
  entry: EntryDescriptorSchema.optional()
3673
- }).strict();
3677
+ });
3678
+ /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
3679
+ var RecordKindDeclarationSchema = RecordKindDeclarationShape.strict();
3680
+ /**
3681
+ * A declaration as the engine's catalog stored it: an unknown key is DROPPED,
3682
+ * because a row written by an older engine must never stop records from
3683
+ * starting. Only for rows read back from the catalog, never for input.
3684
+ */
3685
+ var StoredRecordKindDeclarationSchema = RecordKindDeclarationShape;
3674
3686
  /** The kind names: lower-case, dash separated, short enough for an index name. */
3675
3687
  var RecordKindNameSchema = zod.z.string().regex(/^[a-z][a-z0-9-]{0,47}$/);
3676
3688
  /** Cross-field rules a declaration must satisfy. Throws {@link DeclarationRefusedError}. */
@@ -3761,6 +3773,25 @@ function declarationsOf(kinds) {
3761
3773
  return Object.fromEntries(Object.entries(kinds).map(([name, kind]) => [name, kind.declaration]));
3762
3774
  }
3763
3775
  //#endregion
3776
+ //#region src/records/transact-rules.ts
3777
+ /**
3778
+ * The scope rule of `transact`, shared by the engine, the provider and the
3779
+ * in-memory records so they cannot disagree: every op's kind sits in ONE
3780
+ * database file, and when more than one kind is involved they all name the
3781
+ * SAME `transactFamily`. A refusal names the rule; nothing is split silently.
3782
+ */
3783
+ /** Throws {@link TransactRefusedError} unless the kinds may share one transaction. */
3784
+ function assertTransactScope(members) {
3785
+ const byKind = /* @__PURE__ */ new Map();
3786
+ for (const m of members) byKind.set(m.kind, m);
3787
+ const kinds = [...byKind.values()];
3788
+ if (new Set(kinds.map((m) => m.declaration.database)).size > 1) throw new TransactRefusedError(`cross-file: the ops span more than one database file (${kinds.map((m) => `${m.kind}@${m.declaration.database}`).join(", ")}); a transaction lives in ONE file`);
3789
+ if (kinds.length <= 1) return;
3790
+ const missing = kinds.filter((m) => m.declaration.transactFamily === void 0);
3791
+ if (missing.length > 0) throw new TransactRefusedError(`no-family: ${missing.map((m) => m.kind).join(", ")} declare no transactFamily; kinds that must agree through transact share one`);
3792
+ if (new Set(kinds.map((m) => m.declaration.transactFamily)).size > 1) throw new TransactRefusedError(`cross-family: the ops span more than one transactFamily (${kinds.map((m) => `${m.kind}=${String(m.declaration.transactFamily)}`).join(", ")})`);
3793
+ }
3794
+ //#endregion
3764
3795
  //#region src/records/records-access.ts
3765
3796
  function isRecord$2(value) {
3766
3797
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3935,6 +3966,47 @@ var KindHandle = class {
3935
3966
  };
3936
3967
  });
3937
3968
  }
3969
+ async txPut(input, opts = {}) {
3970
+ const checked = this.validate(input.data, input.id);
3971
+ const { body, rollback } = await this.storeSecrets(input.id, checked, "put");
3972
+ return {
3973
+ wire: {
3974
+ op: "put",
3975
+ kind: this.name,
3976
+ record: {
3977
+ ...input,
3978
+ data: toBody(body)
3979
+ },
3980
+ ...opts.ifRev === void 0 ? {} : { ifRev: opts.ifRev }
3981
+ },
3982
+ rollback
3983
+ };
3984
+ }
3985
+ async txPatch(id, partial, opts = {}) {
3986
+ const { body, rollback } = await this.storeSecrets(id, partial, "patch");
3987
+ return {
3988
+ wire: {
3989
+ op: "patch",
3990
+ kind: this.name,
3991
+ id,
3992
+ data: toBody(body),
3993
+ ...opts.at === void 0 ? {} : { at: opts.at },
3994
+ ...opts.expiresAt === void 0 ? {} : { expiresAt: opts.expiresAt },
3995
+ ...opts.ifRev === void 0 ? {} : { ifRev: opts.ifRev }
3996
+ },
3997
+ rollback
3998
+ };
3999
+ }
4000
+ txDelete(id) {
4001
+ return {
4002
+ wire: {
4003
+ op: "delete",
4004
+ kind: this.name,
4005
+ id
4006
+ },
4007
+ rollback: async () => {}
4008
+ };
4009
+ }
3938
4010
  async put(input, opts = {}) {
3939
4011
  const checked = this.validate(input.data, input.id);
3940
4012
  const { body, rollback } = await this.storeSecrets(input.id, checked, "put");
@@ -4089,6 +4161,23 @@ function createRecordsAccess(options) {
4089
4161
  handles.set(definition, handle);
4090
4162
  return handle;
4091
4163
  },
4164
+ async transact(ops) {
4165
+ if (ops.length === 0) return [];
4166
+ const result = await call(() => options.transport.transact({ ops: ops.map((op) => op.wire) })).catch(async (err) => {
4167
+ for (const op of ops) await op.rollback();
4168
+ throw err;
4169
+ });
4170
+ const released = [...result.releasedRefs, ...result.secretRefs];
4171
+ if (released.length > 0 && options.secrets !== null) try {
4172
+ await options.secrets.delete(released);
4173
+ } catch (err) {
4174
+ options.logger?.warn("records: could not remove released keychain values", { meta: {
4175
+ count: released.length,
4176
+ error: err instanceof Error ? err.message : "unknown"
4177
+ } });
4178
+ }
4179
+ return result.results;
4180
+ },
4092
4181
  async deleteDevice(deviceId, opts = {}) {
4093
4182
  const result = await call(() => options.transport.deleteDevice({
4094
4183
  deviceId,
@@ -4634,312 +4723,372 @@ function makeInMemoryRecords(owner = "spec", options = {}) {
4634
4723
  secretRefs: refsOf(decl, gone)
4635
4724
  };
4636
4725
  };
4637
- return {
4638
- records: createRecordsAccess({
4639
- owner,
4640
- transport: {
4641
- declareKinds: async ({ kinds }) => {
4642
- const out = {};
4643
- for (const [name, raw] of Object.entries(kinds)) {
4644
- const decl = RecordKindDeclarationSchema.parse(raw);
4645
- assertValidDeclaration(name, decl);
4646
- const prior = decls.get(name);
4647
- if (prior !== void 0) assertAdditive(name, prior, decl);
4648
- const kindId = kindIds.get(name) ?? kindIds.size + 1;
4649
- kindIds.set(name, kindId);
4650
- const created = decl.indexes.filter((i) => prior?.indexes.every((p) => p.name !== i.name) ?? true).map((i) => i.name);
4651
- const state = prior === void 0 ? "created" : JSON.stringify(prior) === JSON.stringify(decl) ? "unchanged" : "updated";
4652
- decls.set(name, decl);
4653
- out[name] = {
4654
- kindId,
4655
- state,
4656
- createdIndexes: created,
4657
- adoptionPending: null
4658
- };
4659
- }
4660
- return { kinds: out };
4661
- },
4662
- get: async ({ kind, id }) => {
4663
- reading();
4664
- const decl = declOf(kind);
4665
- const row = table(kind).get(id);
4666
- return row === void 0 ? null : view(decl, row);
4667
- },
4668
- getMany: async ({ kind, ids }) => {
4669
- reading();
4670
- const decl = declOf(kind);
4671
- return [...new Set(ids)].sort((a, b) => compareSql(a, b)).flatMap((id) => {
4672
- const row = table(kind).get(id);
4673
- return row === void 0 ? [] : [view(decl, row)];
4674
- });
4675
- },
4676
- secretRefs: async ({ kind, ids }) => {
4677
- reading();
4678
- const decl = declOf(kind);
4679
- const out = {};
4680
- for (const id of ids) {
4681
- const row = table(kind).get(id);
4682
- if (row === void 0) continue;
4683
- const refs = Object.fromEntries(markersOf(decl, row.data));
4684
- if (Object.keys(refs).length > 0) out[id] = refs;
4726
+ const transport = {
4727
+ declareKinds: async ({ kinds }) => {
4728
+ const out = {};
4729
+ for (const [name, raw] of Object.entries(kinds)) {
4730
+ const decl = RecordKindDeclarationSchema.parse(raw);
4731
+ assertValidDeclaration(name, decl);
4732
+ const prior = decls.get(name);
4733
+ if (prior !== void 0) assertAdditive(name, prior, decl);
4734
+ const kindId = kindIds.get(name) ?? kindIds.size + 1;
4735
+ kindIds.set(name, kindId);
4736
+ const created = decl.indexes.filter((i) => prior?.indexes.every((p) => p.name !== i.name) ?? true).map((i) => i.name);
4737
+ const state = prior === void 0 ? "created" : JSON.stringify(prior) === JSON.stringify(decl) ? "unchanged" : "updated";
4738
+ decls.set(name, decl);
4739
+ out[name] = {
4740
+ kindId,
4741
+ state,
4742
+ createdIndexes: created
4743
+ };
4744
+ }
4745
+ return { kinds: out };
4746
+ },
4747
+ get: async ({ kind, id }) => {
4748
+ reading();
4749
+ const decl = declOf(kind);
4750
+ const row = table(kind).get(id);
4751
+ return row === void 0 ? null : view(decl, row);
4752
+ },
4753
+ getMany: async ({ kind, ids }) => {
4754
+ reading();
4755
+ const decl = declOf(kind);
4756
+ return [...new Set(ids)].sort((a, b) => compareSql(a, b)).flatMap((id) => {
4757
+ const row = table(kind).get(id);
4758
+ return row === void 0 ? [] : [view(decl, row)];
4759
+ });
4760
+ },
4761
+ secretRefs: async ({ kind, ids }) => {
4762
+ reading();
4763
+ const decl = declOf(kind);
4764
+ const out = {};
4765
+ for (const id of ids) {
4766
+ const row = table(kind).get(id);
4767
+ if (row === void 0) continue;
4768
+ const refs = Object.fromEntries(markersOf(decl, row.data));
4769
+ if (Object.keys(refs).length > 0) out[id] = refs;
4770
+ }
4771
+ return out;
4772
+ },
4773
+ put: async ({ kind, record, ifRev }) => {
4774
+ writing();
4775
+ const written = writeRecord(kind, record, ifRev, now());
4776
+ afterWrite(kind, [record.deviceId]);
4777
+ return {
4778
+ rev: written.rev,
4779
+ releasedRefs: [...written.released]
4780
+ };
4781
+ },
4782
+ putMany: async ({ kind, records }) => {
4783
+ writing();
4784
+ declOf(kind);
4785
+ if (records.length > 1e3) throw new BatchTooLargeError(`${records.length} records exceed ${RECORD_BATCH_MAX}`);
4786
+ const bytes = records.reduce((sum, r) => sum + Buffer.byteLength(JSON.stringify(r.data), "utf8"), 0);
4787
+ if (bytes > 4194304) throw new BatchTooLargeError(`${bytes} bytes exceed ${RECORD_BATCH_MAX_BYTES}`);
4788
+ const nowMs = now();
4789
+ const before = new Map(table(kind));
4790
+ const ridBefore = nextRid;
4791
+ const released = [];
4792
+ try {
4793
+ for (const rec of records) released.push(...writeRecord(kind, rec, void 0, nowMs).released);
4794
+ } catch (err) {
4795
+ tables.set(kind, before);
4796
+ nextRid = ridBefore;
4797
+ throw err;
4798
+ }
4799
+ let top = -1;
4800
+ for (const r of records) if (/^\d{1,15}$/.test(r.id)) top = Math.max(top, Number(r.id));
4801
+ if (top >= 0) sequences.set(kind, Math.max(sequences.get(kind) ?? 0, top + 1));
4802
+ afterWrite(kind, records.map((r) => r.deviceId));
4803
+ return {
4804
+ count: records.length,
4805
+ releasedRefs: released
4806
+ };
4807
+ },
4808
+ patch: async (input) => {
4809
+ writing();
4810
+ const decl = declOf(input.kind);
4811
+ if (input.at !== void 0 && decl.at !== "required") throw new InvalidRecordError(`${input.kind}: declared at: "none"`);
4812
+ if (input.at === null) throw new InvalidRecordError(`${input.kind}: "at" cannot be removed`);
4813
+ if (input.expiresAt !== void 0 && decl.secretPaths.length > 0) throw new InvalidRecordError(`${input.kind}: a kind with secret fields cannot expire by the engine (its keychain values would be orphaned)`);
4814
+ if (input.expiresAt !== void 0 && "ttlMs" in decl.retention) throw new InvalidRecordError(`${input.kind}: expiresAt is set by the engine`);
4815
+ const before = table(input.kind).get(input.id);
4816
+ const checked = enforceSecrets(owner, input.kind, decl, input.id, input.data, before?.data ?? null, "patch");
4817
+ if (before === void 0) throw new RecordNotFoundError(`${input.kind}/${input.id}: no such row`);
4818
+ if (input.ifRev !== void 0 && before.rev !== input.ifRev) throw new RevConflictError(`${input.kind}/${input.id}: expected rev ${String(input.ifRev)}, found rev ${before.rev}`);
4819
+ const merged = jsonCopy(mergePatch(before.data, jsonCopy(checked.body)));
4820
+ const size = [...JSON.stringify(merged)].length;
4821
+ if (size > decl.maxDataBytes) throw new RecordTooLargeError(`${input.kind}/${input.id}: the patched body is ${size} bytes, over maxDataBytes ${decl.maxDataBytes}`);
4822
+ assertUnique(input.kind, decl, input.id, merged);
4823
+ const rev = before.rev + 1;
4824
+ store(input.kind, before, {
4825
+ ...before,
4826
+ at: input.at ?? before.at,
4827
+ expiresAt: input.expiresAt === void 0 ? before.expiresAt : input.expiresAt,
4828
+ rev,
4829
+ updatedAt: now(),
4830
+ data: merged
4831
+ });
4832
+ const kept = new Set(markersOf(decl, merged).values());
4833
+ return {
4834
+ rev,
4835
+ releasedRefs: [...markersOf(decl, before.data).values()].filter((r) => !kept.has(r))
4836
+ };
4837
+ },
4838
+ delete: async ({ kind, id }) => {
4839
+ writing();
4840
+ const decl = declOf(kind);
4841
+ const row = table(kind).get(id);
4842
+ if (row === void 0) return {
4843
+ deleted: false,
4844
+ secretRefs: []
4845
+ };
4846
+ table(kind).delete(id);
4847
+ return {
4848
+ deleted: true,
4849
+ secretRefs: refsOf(decl, [row])
4850
+ };
4851
+ },
4852
+ deleteMany: async ({ kind, ids }) => {
4853
+ writing();
4854
+ const wanted = new Set(ids);
4855
+ return removeWhere(kind, (row) => wanted.has(row.id));
4856
+ },
4857
+ query: async (q) => {
4858
+ reading();
4859
+ const decl = declOf(q.kind);
4860
+ queries.push({
4861
+ kind: q.kind,
4862
+ limit: q.limit
4863
+ });
4864
+ assertWindowAllowed(q.kind, decl, q);
4865
+ const desc = q.desc === true;
4866
+ const dir = desc ? "DESC" : "ASC";
4867
+ const where = planWhere(q.kind, decl, q.where, { allowIn: false });
4868
+ const cursor = q.cursor === void 0 ? void 0 : decodeCursor(q.cursor);
4869
+ let rows = [...table(q.kind).values()].filter((r) => inWindow(r, q));
4870
+ let keysOf;
4871
+ let keyCount;
4872
+ let tag;
4873
+ if (where !== null) {
4874
+ if (q.order !== void 0) throw new InvalidRecordError(`${q.kind}: with a where the order is the index's; drop "order"`);
4875
+ const fields = where.orderFields;
4876
+ keysOf = (row) => [...fields.map((f) => extractField(row.data, f)), row.rid];
4877
+ keyCount = fields.length;
4878
+ tag = `rx:${where.indexName}:${dir}`;
4879
+ rows = rows.filter((r) => where.matches(r.data));
4880
+ if (cursor !== void 0) {
4881
+ assertCursor(cursor, tag, fields.length + 1);
4882
+ rows = rows.filter((r) => rowValueAfter(keysOf(r), cursor.values, desc));
4883
+ }
4884
+ } else {
4885
+ const order = q.order ?? (decl.at === "required" ? "at" : "id");
4886
+ keyCount = 1;
4887
+ if (order === "at") {
4888
+ if (decl.at !== "required") throw new InvalidRecordError(`${q.kind}: declared at: "none", it has no time order - use order: "id"`);
4889
+ keysOf = (row) => [row.at, row.rid];
4890
+ tag = `at:${dir}`;
4891
+ if (cursor !== void 0) {
4892
+ assertCursor(cursor, tag, 2);
4893
+ rows = rows.filter((r) => rowValueAfter(keysOf(r), cursor.values, desc));
4685
4894
  }
4686
- return out;
4687
- },
4688
- put: async ({ kind, record, ifRev }) => {
4689
- writing();
4690
- const written = writeRecord(kind, record, ifRev, now());
4691
- afterWrite(kind, [record.deviceId]);
4692
- return {
4693
- rev: written.rev,
4694
- releasedRefs: [...written.released]
4695
- };
4696
- },
4697
- putMany: async ({ kind, records }) => {
4698
- writing();
4699
- declOf(kind);
4700
- if (records.length > 1e3) throw new BatchTooLargeError(`${records.length} records exceed ${RECORD_BATCH_MAX}`);
4701
- const bytes = records.reduce((sum, r) => sum + Buffer.byteLength(JSON.stringify(r.data), "utf8"), 0);
4702
- if (bytes > 4194304) throw new BatchTooLargeError(`${bytes} bytes exceed ${RECORD_BATCH_MAX_BYTES}`);
4703
- const nowMs = now();
4704
- const before = new Map(table(kind));
4705
- const ridBefore = nextRid;
4706
- const released = [];
4707
- try {
4708
- for (const rec of records) released.push(...writeRecord(kind, rec, void 0, nowMs).released);
4709
- } catch (err) {
4710
- tables.set(kind, before);
4711
- nextRid = ridBefore;
4712
- throw err;
4895
+ } else {
4896
+ if (q.deviceId !== void 0 || q.atFrom !== void 0 || q.atTo !== void 0) throw new UnindexedFilterError(`${q.kind}: order "id" cannot be combined with a device or time window (use order "at")`);
4897
+ keysOf = (row) => [row.id, row.rid];
4898
+ tag = `id:${dir}`;
4899
+ if (cursor !== void 0) {
4900
+ assertCursor(cursor, tag, 2);
4901
+ const last = cursor.values[0] ?? null;
4902
+ rows = rows.filter((r) => rowValueAfter([r.id], [last], desc));
4713
4903
  }
4714
- let top = -1;
4715
- for (const r of records) if (/^\d{1,15}$/.test(r.id)) top = Math.max(top, Number(r.id));
4716
- if (top >= 0) sequences.set(kind, Math.max(sequences.get(kind) ?? 0, top + 1));
4717
- afterWrite(kind, records.map((r) => r.deviceId));
4718
- return {
4719
- count: records.length,
4720
- releasedRefs: released
4721
- };
4722
- },
4723
- patch: async (input) => {
4724
- writing();
4725
- const decl = declOf(input.kind);
4726
- if (input.at !== void 0 && decl.at !== "required") throw new InvalidRecordError(`${input.kind}: declared at: "none"`);
4727
- if (input.at === null) throw new InvalidRecordError(`${input.kind}: "at" cannot be removed`);
4728
- if (input.expiresAt !== void 0 && decl.secretPaths.length > 0) throw new InvalidRecordError(`${input.kind}: a kind with secret fields cannot expire by the engine (its keychain values would be orphaned)`);
4729
- if (input.expiresAt !== void 0 && "ttlMs" in decl.retention) throw new InvalidRecordError(`${input.kind}: expiresAt is set by the engine`);
4730
- const before = table(input.kind).get(input.id);
4731
- const checked = enforceSecrets(owner, input.kind, decl, input.id, input.data, before?.data ?? null, "patch");
4732
- if (before === void 0) throw new RecordNotFoundError(`${input.kind}/${input.id}: no such row`);
4733
- if (input.ifRev !== void 0 && before.rev !== input.ifRev) throw new RevConflictError(`${input.kind}/${input.id}: expected rev ${String(input.ifRev)}, found rev ${before.rev}`);
4734
- const merged = jsonCopy(mergePatch(before.data, jsonCopy(checked.body)));
4735
- const size = [...JSON.stringify(merged)].length;
4736
- if (size > decl.maxDataBytes) throw new RecordTooLargeError(`${input.kind}/${input.id}: the patched body is ${size} bytes, over maxDataBytes ${decl.maxDataBytes}`);
4737
- assertUnique(input.kind, decl, input.id, merged);
4738
- const rev = before.rev + 1;
4739
- store(input.kind, before, {
4740
- ...before,
4741
- at: input.at ?? before.at,
4742
- expiresAt: input.expiresAt === void 0 ? before.expiresAt : input.expiresAt,
4743
- rev,
4744
- updatedAt: now(),
4745
- data: merged
4904
+ }
4905
+ }
4906
+ rows.sort((a, b) => (desc ? -1 : 1) * compareKeys(keysOf(a), keysOf(b)));
4907
+ const page = rows.slice(0, q.limit);
4908
+ const more = rows.length > q.limit;
4909
+ const last = page.at(-1);
4910
+ let nextCursor = null;
4911
+ if (more && last !== void 0) {
4912
+ const all = keysOf(last);
4913
+ const keys = [];
4914
+ for (let i = 0; i < keyCount; i += 1) {
4915
+ const value = all[i];
4916
+ if (typeof value !== "string" && typeof value !== "number") throw new InvalidRecordError(`${q.kind}: a row has no value for an ordering field, so this query cannot be paged`);
4917
+ keys.push(value);
4918
+ }
4919
+ keys.push(last.rid);
4920
+ nextCursor = encodeCursor(tag, keys);
4921
+ }
4922
+ return {
4923
+ records: page.map((r) => view(decl, r)),
4924
+ nextCursor
4925
+ };
4926
+ },
4927
+ count: async (q) => {
4928
+ reading();
4929
+ const decl = declOf(q.kind);
4930
+ assertWindowAllowed(q.kind, decl, q);
4931
+ const where = planWhere(q.kind, decl, q.where, { allowIn: true });
4932
+ let n = 0;
4933
+ for (const row of table(q.kind).values()) if (inWindow(row, q) && (where === null || where.matches(row.data))) n += 1;
4934
+ return { count: n };
4935
+ },
4936
+ histogram: async (q) => {
4937
+ reading();
4938
+ const decl = declOf(q.kind);
4939
+ assertWindowAllowed(q.kind, decl, {
4940
+ deviceId: q.deviceId,
4941
+ atFrom: q.from
4942
+ });
4943
+ if (!(q.to > q.from)) throw new InvalidRecordError(`${q.kind}: histogram needs from < to`);
4944
+ if (Math.ceil((q.to - q.from) / q.bucketMs) > 1e4) throw new InvalidRecordError(`${q.kind}: histogram would exceed ${HISTOGRAM_MAX_BUCKETS} buckets`);
4945
+ const from = Math.floor(q.from);
4946
+ const to = Math.ceil(q.to);
4947
+ const step = Math.trunc(q.bucketMs);
4948
+ const counts = /* @__PURE__ */ new Map();
4949
+ for (const row of table(q.kind).values()) {
4950
+ if (q.deviceId !== void 0 && row.deviceId !== q.deviceId) continue;
4951
+ if (row.at === null || row.at < from || row.at >= to) continue;
4952
+ const b = Math.trunc((row.at - from) / step);
4953
+ counts.set(b, (counts.get(b) ?? 0) + 1);
4954
+ }
4955
+ return { buckets: [...counts.entries()].sort(([a], [b]) => a - b).map(([b, count]) => ({
4956
+ bucketStart: from + b * q.bucketMs,
4957
+ count
4958
+ })) };
4959
+ },
4960
+ aggregate: async (q) => {
4961
+ reading();
4962
+ const decl = declOf(q.kind);
4963
+ if (q.op !== "count" && q.field === void 0) throw new InvalidRecordError(`${q.kind}: ${q.op} needs a field`);
4964
+ const extra = [q.field, q.groupBy].filter((f) => f !== void 0);
4965
+ if (extra.length === 0 && q.where === void 0) throw new InvalidRecordError(`${q.kind}: a count with no field and no groupBy is "count"`);
4966
+ const where = planWhere(q.kind, decl, q.where, {
4967
+ allowIn: true,
4968
+ extraFields: extra,
4969
+ groupBy: q.groupBy
4970
+ });
4971
+ if (where === null) throw new InvalidRecordError(`${q.kind}: aggregate has nothing to read`);
4972
+ const rows = [...table(q.kind).values()].filter((r) => where.matches(r.data));
4973
+ const groups = /* @__PURE__ */ new Map();
4974
+ if (q.groupBy === void 0) groups.set(null, rows);
4975
+ else {
4976
+ const by = q.groupBy;
4977
+ for (const row of rows) {
4978
+ const g = extractField(row.data, by);
4979
+ groups.set(g, [...groups.get(g) ?? [], row]);
4980
+ }
4981
+ }
4982
+ return { rows: [...groups.entries()].sort(([a], [b]) => compareSql(a, b)).map(([group, members]) => ({
4983
+ group,
4984
+ value: aggregateOf(q.op, q.field, members)
4985
+ })) };
4986
+ },
4987
+ deleteWhere: async (q) => {
4988
+ writing();
4989
+ const decl = declOf(q.kind);
4990
+ if (q.deviceId === void 0 && q.atBefore === void 0 && (q.where === void 0 || Object.keys(q.where).length === 0)) throw new FilterRequiredError(`${q.kind}: deleteWhere needs deviceId, atBefore or where - there is no "delete all"`);
4991
+ assertWindowAllowed(q.kind, decl, {
4992
+ deviceId: q.deviceId,
4993
+ atFrom: q.atBefore
4994
+ });
4995
+ const where = planWhere(q.kind, decl, q.where, { allowIn: true });
4996
+ return removeWhere(q.kind, (row) => {
4997
+ if (where !== null && !where.matches(row.data)) return false;
4998
+ if (q.deviceId !== void 0 && row.deviceId !== q.deviceId) return false;
4999
+ if (q.atBefore !== void 0 && (row.at === null || row.at >= q.atBefore)) return false;
5000
+ return true;
5001
+ });
5002
+ },
5003
+ transact: async ({ ops }) => {
5004
+ writing();
5005
+ assertTransactScope(ops.map((op) => ({
5006
+ kind: op.kind,
5007
+ declaration: declOf(op.kind)
5008
+ })));
5009
+ const before = new Map([...tables].map(([name, rows]) => [name, new Map(rows)]));
5010
+ const ridBefore = nextRid;
5011
+ const results = [];
5012
+ const releasedRefs = [];
5013
+ const secretRefs = [];
5014
+ try {
5015
+ for (const op of ops) if (op.op === "put") {
5016
+ const r = await transport.put({
5017
+ kind: op.kind,
5018
+ record: op.record,
5019
+ ...op.ifRev === void 0 ? {} : { ifRev: op.ifRev }
4746
5020
  });
4747
- const kept = new Set(markersOf(decl, merged).values());
4748
- return {
4749
- rev,
4750
- releasedRefs: [...markersOf(decl, before.data).values()].filter((r) => !kept.has(r))
4751
- };
4752
- },
4753
- delete: async ({ kind, id }) => {
4754
- writing();
4755
- const decl = declOf(kind);
4756
- const row = table(kind).get(id);
4757
- if (row === void 0) return {
4758
- deleted: false,
4759
- secretRefs: []
4760
- };
4761
- table(kind).delete(id);
4762
- return {
4763
- deleted: true,
4764
- secretRefs: refsOf(decl, [row])
4765
- };
4766
- },
4767
- deleteMany: async ({ kind, ids }) => {
4768
- writing();
4769
- const wanted = new Set(ids);
4770
- return removeWhere(kind, (row) => wanted.has(row.id));
4771
- },
4772
- query: async (q) => {
4773
- reading();
4774
- const decl = declOf(q.kind);
4775
- queries.push({
4776
- kind: q.kind,
4777
- limit: q.limit
5021
+ releasedRefs.push(...r.releasedRefs);
5022
+ results.push({
5023
+ rev: r.rev,
5024
+ deleted: false
4778
5025
  });
4779
- assertWindowAllowed(q.kind, decl, q);
4780
- const desc = q.desc === true;
4781
- const dir = desc ? "DESC" : "ASC";
4782
- const where = planWhere(q.kind, decl, q.where, { allowIn: false });
4783
- const cursor = q.cursor === void 0 ? void 0 : decodeCursor(q.cursor);
4784
- let rows = [...table(q.kind).values()].filter((r) => inWindow(r, q));
4785
- let keysOf;
4786
- let keyCount;
4787
- let tag;
4788
- if (where !== null) {
4789
- if (q.order !== void 0) throw new InvalidRecordError(`${q.kind}: with a where the order is the index's; drop "order"`);
4790
- const fields = where.orderFields;
4791
- keysOf = (row) => [...fields.map((f) => extractField(row.data, f)), row.rid];
4792
- keyCount = fields.length;
4793
- tag = `rx:${where.indexName}:${dir}`;
4794
- rows = rows.filter((r) => where.matches(r.data));
4795
- if (cursor !== void 0) {
4796
- assertCursor(cursor, tag, fields.length + 1);
4797
- rows = rows.filter((r) => rowValueAfter(keysOf(r), cursor.values, desc));
4798
- }
4799
- } else {
4800
- const order = q.order ?? (decl.at === "required" ? "at" : "id");
4801
- keyCount = 1;
4802
- if (order === "at") {
4803
- if (decl.at !== "required") throw new InvalidRecordError(`${q.kind}: declared at: "none", it has no time order - use order: "id"`);
4804
- keysOf = (row) => [row.at, row.rid];
4805
- tag = `at:${dir}`;
4806
- if (cursor !== void 0) {
4807
- assertCursor(cursor, tag, 2);
4808
- rows = rows.filter((r) => rowValueAfter(keysOf(r), cursor.values, desc));
4809
- }
4810
- } else {
4811
- if (q.deviceId !== void 0 || q.atFrom !== void 0 || q.atTo !== void 0) throw new UnindexedFilterError(`${q.kind}: order "id" cannot be combined with a device or time window (use order "at")`);
4812
- keysOf = (row) => [row.id, row.rid];
4813
- tag = `id:${dir}`;
4814
- if (cursor !== void 0) {
4815
- assertCursor(cursor, tag, 2);
4816
- const last = cursor.values[0] ?? null;
4817
- rows = rows.filter((r) => rowValueAfter([r.id], [last], desc));
4818
- }
4819
- }
4820
- }
4821
- rows.sort((a, b) => (desc ? -1 : 1) * compareKeys(keysOf(a), keysOf(b)));
4822
- const page = rows.slice(0, q.limit);
4823
- const more = rows.length > q.limit;
4824
- const last = page.at(-1);
4825
- let nextCursor = null;
4826
- if (more && last !== void 0) {
4827
- const all = keysOf(last);
4828
- const keys = [];
4829
- for (let i = 0; i < keyCount; i += 1) {
4830
- const value = all[i];
4831
- if (typeof value !== "string" && typeof value !== "number") throw new InvalidRecordError(`${q.kind}: a row has no value for an ordering field, so this query cannot be paged`);
4832
- keys.push(value);
4833
- }
4834
- keys.push(last.rid);
4835
- nextCursor = encodeCursor(tag, keys);
4836
- }
4837
- return {
4838
- records: page.map((r) => view(decl, r)),
4839
- nextCursor
4840
- };
4841
- },
4842
- count: async (q) => {
4843
- reading();
4844
- const decl = declOf(q.kind);
4845
- assertWindowAllowed(q.kind, decl, q);
4846
- const where = planWhere(q.kind, decl, q.where, { allowIn: true });
4847
- let n = 0;
4848
- for (const row of table(q.kind).values()) if (inWindow(row, q) && (where === null || where.matches(row.data))) n += 1;
4849
- return { count: n };
4850
- },
4851
- histogram: async (q) => {
4852
- reading();
4853
- const decl = declOf(q.kind);
4854
- assertWindowAllowed(q.kind, decl, {
4855
- deviceId: q.deviceId,
4856
- atFrom: q.from
5026
+ } else if (op.op === "patch") {
5027
+ const r = await transport.patch({
5028
+ kind: op.kind,
5029
+ id: op.id,
5030
+ data: op.data,
5031
+ ...op.at === void 0 ? {} : { at: op.at },
5032
+ ...op.expiresAt === void 0 ? {} : { expiresAt: op.expiresAt },
5033
+ ...op.ifRev === void 0 ? {} : { ifRev: op.ifRev }
4857
5034
  });
4858
- if (!(q.to > q.from)) throw new InvalidRecordError(`${q.kind}: histogram needs from < to`);
4859
- if (Math.ceil((q.to - q.from) / q.bucketMs) > 1e4) throw new InvalidRecordError(`${q.kind}: histogram would exceed ${HISTOGRAM_MAX_BUCKETS} buckets`);
4860
- const from = Math.floor(q.from);
4861
- const to = Math.ceil(q.to);
4862
- const step = Math.trunc(q.bucketMs);
4863
- const counts = /* @__PURE__ */ new Map();
4864
- for (const row of table(q.kind).values()) {
4865
- if (q.deviceId !== void 0 && row.deviceId !== q.deviceId) continue;
4866
- if (row.at === null || row.at < from || row.at >= to) continue;
4867
- const b = Math.trunc((row.at - from) / step);
4868
- counts.set(b, (counts.get(b) ?? 0) + 1);
4869
- }
4870
- return { buckets: [...counts.entries()].sort(([a], [b]) => a - b).map(([b, count]) => ({
4871
- bucketStart: from + b * q.bucketMs,
4872
- count
4873
- })) };
4874
- },
4875
- aggregate: async (q) => {
4876
- reading();
4877
- const decl = declOf(q.kind);
4878
- if (q.op !== "count" && q.field === void 0) throw new InvalidRecordError(`${q.kind}: ${q.op} needs a field`);
4879
- const extra = [q.field, q.groupBy].filter((f) => f !== void 0);
4880
- if (extra.length === 0 && q.where === void 0) throw new InvalidRecordError(`${q.kind}: a count with no field and no groupBy is "count"`);
4881
- const where = planWhere(q.kind, decl, q.where, {
4882
- allowIn: true,
4883
- extraFields: extra,
4884
- groupBy: q.groupBy
5035
+ releasedRefs.push(...r.releasedRefs);
5036
+ results.push({
5037
+ rev: r.rev,
5038
+ deleted: false
4885
5039
  });
4886
- if (where === null) throw new InvalidRecordError(`${q.kind}: aggregate has nothing to read`);
4887
- const rows = [...table(q.kind).values()].filter((r) => where.matches(r.data));
4888
- const groups = /* @__PURE__ */ new Map();
4889
- if (q.groupBy === void 0) groups.set(null, rows);
4890
- else {
4891
- const by = q.groupBy;
4892
- for (const row of rows) {
4893
- const g = extractField(row.data, by);
4894
- groups.set(g, [...groups.get(g) ?? [], row]);
4895
- }
4896
- }
4897
- return { rows: [...groups.entries()].sort(([a], [b]) => compareSql(a, b)).map(([group, members]) => ({
4898
- group,
4899
- value: aggregateOf(q.op, q.field, members)
4900
- })) };
4901
- },
4902
- deleteWhere: async (q) => {
4903
- writing();
4904
- const decl = declOf(q.kind);
4905
- if (q.deviceId === void 0 && q.atBefore === void 0 && (q.where === void 0 || Object.keys(q.where).length === 0)) throw new FilterRequiredError(`${q.kind}: deleteWhere needs deviceId, atBefore or where - there is no "delete all"`);
4906
- assertWindowAllowed(q.kind, decl, {
4907
- deviceId: q.deviceId,
4908
- atFrom: q.atBefore
5040
+ } else {
5041
+ const r = await transport.delete({
5042
+ kind: op.kind,
5043
+ id: op.id
4909
5044
  });
4910
- const where = planWhere(q.kind, decl, q.where, { allowIn: true });
4911
- return removeWhere(q.kind, (row) => {
4912
- if (where !== null && !where.matches(row.data)) return false;
4913
- if (q.deviceId !== void 0 && row.deviceId !== q.deviceId) return false;
4914
- if (q.atBefore !== void 0 && (row.at === null || row.at >= q.atBefore)) return false;
4915
- return true;
5045
+ secretRefs.push(...r.secretRefs);
5046
+ results.push({
5047
+ rev: null,
5048
+ deleted: r.deleted
4916
5049
  });
4917
- },
4918
- deleteDevice: async ({ deviceId, kinds }) => {
4919
- writing();
4920
- const deleted = {};
4921
- const secretRefs = [];
4922
- for (const [kind, decl] of decls) {
4923
- if (decl.deviceRemoval !== "delete") continue;
4924
- if (kinds !== void 0 && !kinds.includes(kind)) continue;
4925
- const result = removeWhere(kind, (row) => row.deviceId === deviceId);
4926
- deleted[kind] = result.deleted;
4927
- secretRefs.push(...result.secretRefs);
4928
- }
4929
- return {
4930
- deleted,
4931
- secretRefs
4932
- };
4933
- },
4934
- allocateId: async ({ kind }) => {
4935
- writing();
4936
- declOf(kind);
4937
- const id = sequences.get(kind) ?? 1;
4938
- sequences.set(kind, id + 1);
4939
- pendingAllocated.add(`${kind}:${id}`);
4940
- return { id };
4941
5050
  }
4942
- },
5051
+ } catch (err) {
5052
+ tables.clear();
5053
+ for (const [name, rows] of before) tables.set(name, rows);
5054
+ nextRid = ridBefore;
5055
+ throw err;
5056
+ }
5057
+ return {
5058
+ results,
5059
+ releasedRefs,
5060
+ secretRefs
5061
+ };
5062
+ },
5063
+ deleteDevice: async ({ deviceId, kinds }) => {
5064
+ writing();
5065
+ const deleted = {};
5066
+ const secretRefs = [];
5067
+ for (const [kind, decl] of decls) {
5068
+ if (decl.deviceRemoval !== "delete") continue;
5069
+ if (kinds !== void 0 && !kinds.includes(kind)) continue;
5070
+ const result = removeWhere(kind, (row) => row.deviceId === deviceId);
5071
+ deleted[kind] = result.deleted;
5072
+ secretRefs.push(...result.secretRefs);
5073
+ }
5074
+ return {
5075
+ deleted,
5076
+ secretRefs
5077
+ };
5078
+ },
5079
+ allocateId: async ({ kind }) => {
5080
+ writing();
5081
+ declOf(kind);
5082
+ const id = sequences.get(kind) ?? 1;
5083
+ sequences.set(kind, id + 1);
5084
+ pendingAllocated.add(`${kind}:${id}`);
5085
+ return { id };
5086
+ }
5087
+ };
5088
+ return {
5089
+ records: createRecordsAccess({
5090
+ owner,
5091
+ transport,
4943
5092
  secrets: makeSpecKeychain(owner).access
4944
5093
  }),
4945
5094
  tables,
@@ -8556,6 +8705,12 @@ Object.defineProperty(exports, "SHARE_VIEW_KINDS", {
8556
8705
  return SHARE_VIEW_KINDS;
8557
8706
  }
8558
8707
  });
8708
+ Object.defineProperty(exports, "StoredRecordKindDeclarationSchema", {
8709
+ enumerable: true,
8710
+ get: function() {
8711
+ return StoredRecordKindDeclarationSchema;
8712
+ }
8713
+ });
8559
8714
  Object.defineProperty(exports, "StreamSourceEntrySchema", {
8560
8715
  enumerable: true,
8561
8716
  get: function() {
@@ -8580,6 +8735,12 @@ Object.defineProperty(exports, "SubscribeAudioChunksResultSchema", {
8580
8735
  return SubscribeAudioChunksResultSchema;
8581
8736
  }
8582
8737
  });
8738
+ Object.defineProperty(exports, "TransactRefusedError", {
8739
+ enumerable: true,
8740
+ get: function() {
8741
+ return TransactRefusedError;
8742
+ }
8743
+ });
8583
8744
  Object.defineProperty(exports, "UnindexedFilterError", {
8584
8745
  enumerable: true,
8585
8746
  get: function() {
@@ -8658,6 +8819,12 @@ Object.defineProperty(exports, "asString", {
8658
8819
  return asString;
8659
8820
  }
8660
8821
  });
8822
+ Object.defineProperty(exports, "assertTransactScope", {
8823
+ enumerable: true,
8824
+ get: function() {
8825
+ return assertTransactScope;
8826
+ }
8827
+ });
8661
8828
  Object.defineProperty(exports, "assertValidDeclaration", {
8662
8829
  enumerable: true,
8663
8830
  get: function() {