@proveanything/smartlinks 2.1.15 → 2.1.17

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.
package/dist/openapi.yaml CHANGED
@@ -9141,6 +9141,35 @@ paths:
9141
9141
  description: Unauthorized
9142
9142
  404:
9143
9143
  description: Not found
9144
+ /admin/partner/{partnerId}/tags/attempts:
9145
+ get:
9146
+ tags:
9147
+ - partner
9148
+ summary: "What phones reported about one chip (errors.report with entityType 'tag'), newest first: a tag that failed to program or register, with the host's NFC log in context."
9149
+ operationId: partner_tagAttempts
9150
+ security:
9151
+ - bearerAuth: []
9152
+ parameters:
9153
+ - name: partnerId
9154
+ in: path
9155
+ required: true
9156
+ schema:
9157
+ type: string
9158
+ responses:
9159
+ 200:
9160
+ description: Success
9161
+ content:
9162
+ application/json:
9163
+ schema:
9164
+ type: array
9165
+ items:
9166
+ $ref: "#/components/schemas/ClientErrorEvent"
9167
+ 400:
9168
+ description: Bad request
9169
+ 401:
9170
+ description: Unauthorized
9171
+ 404:
9172
+ description: Not found
9144
9173
  /admin/partner/{partnerId}/tags/lookup:
9145
9174
  get:
9146
9175
  tags:
@@ -14952,6 +14981,38 @@ paths:
14952
14981
  description: Unauthorized
14953
14982
  404:
14954
14983
  description: Not found
14984
+ /{groupId}/members:
14985
+ post:
14986
+ tags:
14987
+ - ranges
14988
+ summary: Put an existing tag of the set into a group (by code or chip UID), assigning it if a collection is given.
14989
+ operationId: ranges_addMember
14990
+ security: []
14991
+ parameters:
14992
+ - name: groupId
14993
+ in: path
14994
+ required: true
14995
+ schema:
14996
+ type: string
14997
+ responses:
14998
+ 200:
14999
+ description: Success
15000
+ content:
15001
+ application/json:
15002
+ schema:
15003
+ $ref: "#/components/schemas/AddClaimSetRangeMemberResponse"
15004
+ 400:
15005
+ description: Bad request
15006
+ 401:
15007
+ description: Unauthorized
15008
+ 404:
15009
+ description: Not found
15010
+ requestBody:
15011
+ required: true
15012
+ content:
15013
+ application/json:
15014
+ schema:
15015
+ $ref: "#/components/schemas/AddClaimSetRangeMemberRequest"
14955
15016
  /{rangeId}:
14956
15017
  get:
14957
15018
  tags:
@@ -15041,6 +15102,38 @@ paths:
15041
15102
  application/json:
15042
15103
  schema:
15043
15104
  $ref: "#/components/schemas/AssignClaimSetRangeRequest"
15105
+ /{rangeId}/close:
15106
+ post:
15107
+ tags:
15108
+ - ranges
15109
+ summary: "Close an open range: it becomes a range with the quantity it reached (removed if nothing was minted)."
15110
+ operationId: ranges_close
15111
+ security: []
15112
+ parameters:
15113
+ - name: rangeId
15114
+ in: path
15115
+ required: true
15116
+ schema:
15117
+ type: string
15118
+ responses:
15119
+ 200:
15120
+ description: Success
15121
+ content:
15122
+ application/json:
15123
+ schema:
15124
+ $ref: "#/components/schemas/CloseClaimSetRangeResponse"
15125
+ 400:
15126
+ description: Bad request
15127
+ 401:
15128
+ description: Unauthorized
15129
+ 404:
15130
+ description: Not found
15131
+ requestBody:
15132
+ required: true
15133
+ content:
15134
+ application/json:
15135
+ schema:
15136
+ $ref: "#/components/schemas/ClaimSetScope"
15044
15137
  /{rangeId}/export:
15045
15138
  get:
15046
15139
  tags:
@@ -20764,6 +20857,56 @@ components:
20764
20857
  required:
20765
20858
  - slots
20766
20859
  - blocks
20860
+ AppHistoryEvent:
20861
+ type: object
20862
+ properties:
20863
+ id:
20864
+ type: string
20865
+ at:
20866
+ type: string
20867
+ type:
20868
+ type: string
20869
+ entityId:
20870
+ type: string
20871
+ data:
20872
+ type: object
20873
+ additionalProperties: true
20874
+ actorType:
20875
+ type: string
20876
+ enum:
20877
+ - user
20878
+ - contact
20879
+ actorId:
20880
+ type: string
20881
+ required:
20882
+ - id
20883
+ - at
20884
+ - type
20885
+ - entityId
20886
+ - data
20887
+ - actorType
20888
+ - actorId
20889
+ AppHistoryQuery:
20890
+ type: object
20891
+ properties:
20892
+ types:
20893
+ type: array
20894
+ items:
20895
+ type: object
20896
+ additionalProperties: true
20897
+ entityId:
20898
+ type: string
20899
+ data:
20900
+ type: object
20901
+ additionalProperties:
20902
+ type: object
20903
+ additionalProperties: true
20904
+ from:
20905
+ type: string
20906
+ to:
20907
+ type: string
20908
+ limit:
20909
+ type: number
20767
20910
  ServerFunctionSl:
20768
20911
  type: object
20769
20912
  properties:
@@ -20772,6 +20915,9 @@ components:
20772
20915
  appRecords:
20773
20916
  type: object
20774
20917
  additionalProperties: true
20918
+ history:
20919
+ type: object
20920
+ additionalProperties: true
20775
20921
  counters:
20776
20922
  $ref: "#/components/schemas/ServerFunctionCounters"
20777
20923
  tags:
@@ -20793,6 +20939,7 @@ components:
20793
20939
  required:
20794
20940
  - bookings
20795
20941
  - appRecords
20942
+ - history
20796
20943
  - counters
20797
20944
  - tags
20798
20945
  - contacts
@@ -21568,6 +21715,9 @@ components:
21568
21715
  enum:
21569
21716
  - caller
21570
21717
  - collection
21718
+ RecordTransactionOp:
21719
+ type: object
21720
+ additionalProperties: true
21571
21721
  PaginatedResponse:
21572
21722
  type: object
21573
21723
  properties:
@@ -21617,6 +21767,19 @@ components:
21617
21767
  expires_at:
21618
21768
  type: object
21619
21769
  additionalProperties: true
21770
+ updated_at:
21771
+ type: object
21772
+ additionalProperties: true
21773
+ data:
21774
+ type: object
21775
+ additionalProperties:
21776
+ type: object
21777
+ additionalProperties: true
21778
+ dataRange:
21779
+ type: object
21780
+ additionalProperties:
21781
+ type: object
21782
+ additionalProperties: true
21620
21783
  groupBy:
21621
21784
  type: array
21622
21785
  items:
@@ -25919,6 +26082,8 @@ components:
25919
26082
  type: string
25920
26083
  namespace:
25921
26084
  type: string
26085
+ kind:
26086
+ $ref: "#/components/schemas/ClaimSetRangeKind"
25922
26087
  name:
25923
26088
  type: string
25924
26089
  notes:
@@ -25972,6 +26137,7 @@ components:
25972
26137
  - id
25973
26138
  - claimSetId
25974
26139
  - namespace
26140
+ - kind
25975
26141
  - name
25976
26142
  - notes
25977
26143
  - startIndex
@@ -25995,6 +26161,8 @@ components:
25995
26161
  CreateClaimSetRangeRequest:
25996
26162
  type: object
25997
26163
  properties:
26164
+ kind:
26165
+ $ref: "#/components/schemas/ClaimSetRangeKind"
25998
26166
  name:
25999
26167
  type: string
26000
26168
  quantity:
@@ -26018,7 +26186,6 @@ components:
26018
26186
  type: string
26019
26187
  required:
26020
26188
  - name
26021
- - quantity
26022
26189
  UpdateClaimSetRangeRequest:
26023
26190
  type: object
26024
26191
  properties:
@@ -26062,6 +26229,10 @@ components:
26062
26229
  type: string
26063
26230
  proofId:
26064
26231
  type: string
26232
+ rangeId:
26233
+ type: string
26234
+ groupId:
26235
+ type: string
26065
26236
  programmedAt:
26066
26237
  type: string
26067
26238
  scans:
@@ -26075,6 +26246,8 @@ components:
26075
26246
  - productId
26076
26247
  - batchId
26077
26248
  - proofId
26249
+ - rangeId
26250
+ - groupId
26078
26251
  - programmedAt
26079
26252
  - scans
26080
26253
  ClaimSetRangeTagsPage:
@@ -26103,6 +26276,41 @@ components:
26103
26276
  required:
26104
26277
  - url
26105
26278
  - remaining
26279
+ AddClaimSetRangeMemberRequest:
26280
+ type: object
26281
+ properties:
26282
+ code:
26283
+ type: string
26284
+ uid:
26285
+ type: string
26286
+ collectionId:
26287
+ type: string
26288
+ productId:
26289
+ type: string
26290
+ batchId:
26291
+ type: string
26292
+ data:
26293
+ type: object
26294
+ additionalProperties: true
26295
+ clear:
26296
+ type: boolean
26297
+ AddClaimSetRangeMemberResponse:
26298
+ type: object
26299
+ properties:
26300
+ tag:
26301
+ $ref: "#/components/schemas/ClaimSetRangeTag"
26302
+ range:
26303
+ $ref: "#/components/schemas/ClaimSetRange"
26304
+ assigned:
26305
+ type: object
26306
+ additionalProperties: true
26307
+ movedFrom:
26308
+ type: string
26309
+ required:
26310
+ - tag
26311
+ - range
26312
+ - assigned
26313
+ - movedFrom
26106
26314
  ImportClaimSetRangeUidsRequest:
26107
26315
  type: object
26108
26316
  properties:
@@ -26272,12 +26480,15 @@ components:
26272
26480
  $ref: "#/components/schemas/ClaimSetRangeTag"
26273
26481
  range:
26274
26482
  $ref: "#/components/schemas/ClaimSetRange"
26483
+ group:
26484
+ $ref: "#/components/schemas/ClaimSetRange"
26275
26485
  required:
26276
26486
  - namespace
26277
26487
  - claimSetId
26278
26488
  - set
26279
26489
  - tag
26280
26490
  - range
26491
+ - group
26281
26492
  CreatePartnerClaimSetRequest:
26282
26493
  type: object
26283
26494
  properties:
@@ -26303,6 +26514,12 @@ components:
26303
26514
  - ntag424
26304
26515
  - ntag21x
26305
26516
  - qr
26517
+ ClaimSetRangeKind:
26518
+ type: string
26519
+ enum:
26520
+ - range
26521
+ - open
26522
+ - group
26306
26523
  ClaimSetRangeStatus:
26307
26524
  type: string
26308
26525
  enum:
@@ -26311,6 +26528,11 @@ components:
26311
26528
  - sent
26312
26529
  - received
26313
26530
  - assigned
26531
+ - open
26532
+ - group
26533
+ CloseClaimSetRangeResponse:
26534
+ type: object
26535
+ additionalProperties: true
26314
26536
  ClaimSetImportResult:
26315
26537
  type: string
26316
26538
  enum:
@@ -26324,6 +26546,92 @@ components:
26324
26546
  - duplicate_uid
26325
26547
  - uid_in_use
26326
26548
  - uid_conflict
26549
+ ClientErrorReport:
26550
+ type: object
26551
+ properties:
26552
+ app:
26553
+ type: string
26554
+ appVersion:
26555
+ type: string
26556
+ collectionId:
26557
+ type: string
26558
+ operation:
26559
+ type: string
26560
+ severity:
26561
+ type: string
26562
+ enum:
26563
+ - debug
26564
+ - info
26565
+ - warning
26566
+ - error
26567
+ - critical
26568
+ errorCode:
26569
+ type: string
26570
+ errorType:
26571
+ type: string
26572
+ message:
26573
+ type: string
26574
+ stack:
26575
+ type: string
26576
+ entityType:
26577
+ type: string
26578
+ entityId:
26579
+ type: string
26580
+ labels:
26581
+ type: object
26582
+ additionalProperties: true
26583
+ context:
26584
+ type: object
26585
+ additionalProperties: true
26586
+ refId:
26587
+ type: string
26588
+ correlationId:
26589
+ type: string
26590
+ ClientErrorEvent:
26591
+ type: object
26592
+ properties:
26593
+ id:
26594
+ type: string
26595
+ time:
26596
+ type: string
26597
+ event_category:
26598
+ type: string
26599
+ event_action:
26600
+ type: string
26601
+ log_level:
26602
+ type: string
26603
+ error_code:
26604
+ type: string
26605
+ error_message:
26606
+ type: string
26607
+ service_name:
26608
+ type: string
26609
+ service_version:
26610
+ type: string
26611
+ collection_id:
26612
+ type: string
26613
+ user_id:
26614
+ type: string
26615
+ labels:
26616
+ type: object
26617
+ additionalProperties: true
26618
+ context:
26619
+ type: object
26620
+ additionalProperties: true
26621
+ required:
26622
+ - id
26623
+ - time
26624
+ - event_category
26625
+ - event_action
26626
+ - log_level
26627
+ - error_code
26628
+ - error_message
26629
+ - service_name
26630
+ - service_version
26631
+ - collection_id
26632
+ - user_id
26633
+ - labels
26634
+ - context
26327
26635
  Collection:
26328
26636
  type: object
26329
26637
  properties:
@@ -16,7 +16,11 @@ export interface TestSlImpl {
16
16
  get?(id: string): any;
17
17
  query?(params: any): any;
18
18
  listTypes?(): any;
19
+ adjust?(id: string, by: Record<string, number>, opts?: any): any;
20
+ aggregate?(params?: any): any;
19
21
  };
22
+ /** ctx.sl.transaction(ops, { idempotencyKey }) — defaults to the in-memory one (all or none, replay by key). */
23
+ transaction?(ops: any[], opts?: any): any;
20
24
  counters?: {
21
25
  next?(name: string, opts?: any): any;
22
26
  get?(name: string): any;
@@ -108,6 +112,18 @@ export interface FunctionTestMemory {
108
112
  interactions: any[];
109
113
  /** Slots, bookings and allocations of the in-memory bookings engine. */
110
114
  bookings: BookingsMemory;
115
+ /** ctx.sl.transaction results by idempotency key (a repeat replays them). */
116
+ transactions: Record<string, any[]>;
117
+ /** ctx.sl.history events (log ops and history.log), oldest first. */
118
+ history: Array<{
119
+ id: string;
120
+ at: string;
121
+ type: string;
122
+ entityId: string | null;
123
+ data: Record<string, any>;
124
+ actorType: null;
125
+ actorId: null;
126
+ }>;
111
127
  }
112
128
  /** A fresh in-memory store — pass the same one to several test contexts to simulate concurrent callers. */
113
129
  export declare function createFunctionTestMemory(): FunctionTestMemory;
@@ -77,7 +77,7 @@ function allowsSl(p, resource, op) {
77
77
  }
78
78
  /** A fresh in-memory store — pass the same one to several test contexts to simulate concurrent callers. */
79
79
  export function createFunctionTestMemory() {
80
- return { records: [], counters: {}, contacts: [], interactions: [], bookings: createBookingsMemory() };
80
+ return { records: [], counters: {}, contacts: [], interactions: [], bookings: createBookingsMemory(), transactions: {}, history: [] };
81
81
  }
82
82
  const stub = (method, args) => ({ __stub: true, method, args });
83
83
  /**
@@ -101,6 +101,9 @@ export function createFunctionTestContext(opts) {
101
101
  return fn ? fn(...args) : stub(`${resource}.${name}`, args);
102
102
  };
103
103
  const mem = opts.memory || createFunctionTestMemory();
104
+ // A memory built before history / transactions existed still works.
105
+ mem.history = mem.history || [];
106
+ mem.transactions = mem.transactions || {};
104
107
  const ar = impl.appRecords || {};
105
108
  const pr = impl.products || {};
106
109
  const at = impl.attestations || {};
@@ -147,6 +150,60 @@ export function createFunctionTestContext(opts) {
147
150
  return null; Object.assign(r, fields); return Object.assign({}, r); },
148
151
  delete: async (id) => { const r = mem.records.find((x) => x.id === id); if (r)
149
152
  r.deletedAt = new Date().toISOString(); return {}; },
153
+ // Numbers in data changed in place, with atLeast / atMost checked after the change (as the platform).
154
+ adjust: async (id, by = {}, opts = {}) => {
155
+ var _a, _b;
156
+ const r = mem.records.find((x) => x.id === id && !x.deletedAt);
157
+ if (!r)
158
+ throw Object.assign(new Error('Record not found'), { code: 'NOT_FOUND', statusCode: 404 });
159
+ const data = r.data || {};
160
+ const keys = [...new Set([...Object.keys(by), ...Object.keys(opts.atLeast || {}), ...Object.keys(opts.atMost || {})])];
161
+ const bad = keys.find((k) => data[k] != null && typeof data[k] !== 'number');
162
+ if (bad)
163
+ throw Object.assign(new Error(`data.${bad} isn't a number, so it can't be adjusted`), { code: 'NOT_A_NUMBER', statusCode: 400, key: bad });
164
+ const next = {};
165
+ for (const k of keys)
166
+ next[k] = ((_a = data[k]) !== null && _a !== void 0 ? _a : 0) + ((_b = by[k]) !== null && _b !== void 0 ? _b : 0);
167
+ const broken = Object.entries(opts.atLeast || {}).some(([k, n]) => next[k] < n) || Object.entries(opts.atMost || {}).some(([k, n]) => next[k] > n);
168
+ if (broken)
169
+ throw Object.assign(new Error('The change would break its condition (atLeast / atMost)'), { code: 'CONDITION_FAILED', statusCode: 409, current: Object.fromEntries(keys.map((k) => { var _a; return [k, (_a = data[k]) !== null && _a !== void 0 ? _a : 0]; })) });
170
+ r.data = Object.assign(Object.assign({}, data), Object.fromEntries(Object.keys(by).map((k) => [k, next[k]])));
171
+ return Object.assign({}, r);
172
+ },
173
+ // count / sum: / avg: / min: / max: over data numbers, grouped by top-level fields or data keys
174
+ // ('data.location'); filters.data (exact) as the platform. A simple stand-in for tests.
175
+ aggregate: async (params = {}) => {
176
+ const { groupBy = [], metrics = ['count'], filters = {} } = params || {};
177
+ const rows = mem.records.filter((r) => !r.deletedAt &&
178
+ (!filters.record_type || r.recordType === filters.record_type) &&
179
+ (!filters.status || r.status === filters.status) &&
180
+ Object.entries(filters.data || {}).every(([k, v]) => (Array.isArray(v) ? v.map(String) : [String(v)]).includes(String((r.data || {})[k]))));
181
+ const field = (r, f) => (f.startsWith('data.') ? (r.data || {})[f.slice(5)] : r[f === 'record_type' ? 'recordType' : f]);
182
+ const measure = (set) => {
183
+ const out = {};
184
+ for (const m of metrics) {
185
+ if (m === 'count') {
186
+ out.count = set.length;
187
+ continue;
188
+ }
189
+ const [fn, key] = String(m).split(':');
190
+ const nums = set.map((r) => (r.data || {})[key]).filter((v) => typeof v === 'number');
191
+ const sum = nums.reduce((a, b) => a + b, 0);
192
+ out[`${fn}_${key}`] = !nums.length ? null : fn === 'sum' ? sum : fn === 'avg' ? sum / nums.length : fn === 'min' ? Math.min(...nums) : Math.max(...nums);
193
+ }
194
+ return out;
195
+ };
196
+ if (!groupBy.length)
197
+ return measure(rows);
198
+ const buckets = new Map();
199
+ for (const r of rows) {
200
+ const key = JSON.stringify(groupBy.map((f) => { var _a; return (_a = field(r, f)) !== null && _a !== void 0 ? _a : null; }));
201
+ if (!buckets.has(key))
202
+ buckets.set(key, []);
203
+ buckets.get(key).push(r);
204
+ }
205
+ return { groups: [...buckets].map(([key, set]) => (Object.assign(Object.assign({}, Object.fromEntries(groupBy.map((f, i) => [f, JSON.parse(key)[i]]))), measure(set)))) };
206
+ },
150
207
  // Simple equality filter on top-level fields (recordType, proofId, customId, status, …).
151
208
  query: async (params = {}) => {
152
209
  const _a = params || {}, { limit, offset, sort } = _a, where = __rest(_a, ["limit", "offset", "sort"]);
@@ -158,6 +215,60 @@ export function createFunctionTestContext(opts) {
158
215
  return { data, pagination: { total: all.length, limit: size, offset: from, hasMore: from + data.length < all.length } };
159
216
  },
160
217
  };
218
+ // The app's history, in memory (newest first when read, like the platform).
219
+ let nextEventId = 1;
220
+ const memLog = (type, data = {}, opts = {}) => {
221
+ var _a;
222
+ if (typeof type !== 'string' || !/^[a-z][a-z0-9._-]{0,63}$/.test(type))
223
+ throw Object.assign(new Error('history type: lowercase letters, digits and . _ - (e.g. "stock.moved")'), { code: 'INVALID_INPUT', statusCode: 400 });
224
+ const ev = { id: `ev-${nextEventId++}`, at: new Date().toISOString(), type, entityId: (_a = opts.entityId) !== null && _a !== void 0 ? _a : null, data: data || {}, actorType: null, actorId: null };
225
+ mem.history.push(ev);
226
+ return { id: ev.id };
227
+ };
228
+ const memHistoryQuery = async (params = {}) => {
229
+ const types = params.types == null ? null : (Array.isArray(params.types) ? params.types : [params.types]);
230
+ return mem.history
231
+ .filter((e) => (!types || types.includes(e.type)) && (params.entityId == null || e.entityId === params.entityId) &&
232
+ Object.entries(params.data || {}).every(([k, v]) => (Array.isArray(v) ? v.map(String) : [String(v)]).includes(String(e.data[k]))))
233
+ .slice().reverse().slice(0, params.limit || 100).map((e) => (Object.assign({}, e)));
234
+ };
235
+ // All ops or none, like the platform: records are restored when an op fails; a repeat of an
236
+ // idempotencyKey replays the first results.
237
+ const memTransaction = async (ops, opts = {}) => {
238
+ const key = opts.idempotencyKey;
239
+ if (key && mem.transactions[key])
240
+ return { results: mem.transactions[key], replayed: true };
241
+ const before = JSON.parse(JSON.stringify(mem.records));
242
+ const historyBefore = mem.history.length;
243
+ const results = [];
244
+ for (let i = 0; i < ops.length; i++) {
245
+ const o = ops[i] || {};
246
+ try {
247
+ if (o.op === 'create')
248
+ results.push(await memRecords.create(o.record || {}));
249
+ else if (o.op === 'update')
250
+ results.push(await memRecords.update(o.recordId, o.fields || {}));
251
+ else if (o.op === 'adjust')
252
+ results.push(await memRecords.adjust(o.recordId, o.by, { atLeast: o.atLeast, atMost: o.atMost }));
253
+ else if (o.op === 'delete') {
254
+ await memRecords.delete(o.recordId);
255
+ results.push({ deleted: o.recordId });
256
+ }
257
+ else if (o.op === 'log')
258
+ results.push({ logged: memLog(o.type, o.data, { entityId: o.entityId }).id });
259
+ else
260
+ throw Object.assign(new Error(`ops[${i}].op must be create, update, adjust, delete or log`), { code: 'INVALID_INPUT', statusCode: 400 });
261
+ }
262
+ catch (err) {
263
+ mem.records.splice(0, mem.records.length, ...before);
264
+ mem.history.splice(historyBefore);
265
+ throw Object.assign(err, { opIndex: i });
266
+ }
267
+ }
268
+ if (key)
269
+ mem.transactions[key] = results;
270
+ return { results, replayed: false };
271
+ };
161
272
  const recordsFn = (name) => ar[name] ? ar[name].bind(ar) : (impl.appRecords ? undefined : memRecords[name]);
162
273
  const sl = {
163
274
  appRecords: {
@@ -169,6 +280,17 @@ export function createFunctionTestContext(opts) {
169
280
  get: gated('records', 'read', recordsFn('get'), 'get'),
170
281
  query: gated('records', 'read', recordsFn('query'), 'query'),
171
282
  listTypes: gated('records', 'read', recordsFn('listTypes'), 'listTypes'),
283
+ adjust: gated('records', 'write', recordsFn('adjust'), 'adjust'),
284
+ aggregate: gated('records', 'read', recordsFn('aggregate'), 'aggregate'),
285
+ },
286
+ transaction: gated('records', 'write', impl.transaction ? impl.transaction : (impl.appRecords ? undefined : async (ops, opts) => {
287
+ if (Array.isArray(ops) && ops.some((o) => o && o.op === 'log') && !allowsSl(parsed, 'history', 'write'))
288
+ throw new CapabilityError('sl:history:write');
289
+ return memTransaction(ops, opts);
290
+ }), 'transaction'),
291
+ history: {
292
+ log: gated('history', 'write', async (type, data, opts) => memLog(type, data, opts), 'log'),
293
+ query: gated('history', 'read', memHistoryQuery, 'query'),
172
294
  },
173
295
  products: {
174
296
  get: gated('products', 'read', pr.get && pr.get.bind(pr), 'get'),