@proveanything/smartlinks 2.1.16 → 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.
@@ -0,0 +1,10 @@
1
+ import { ClientErrorReport } from "../types/index.js";
2
+ /**
3
+ * Report an error an app saw, into the platform's one error log (the same BigQuery error-events table the
4
+ * server writes to). Best effort: never throws into the flow that failed.
5
+ */
6
+ export declare namespace errors {
7
+ function report(event: ClientErrorReport): Promise<{
8
+ kept: boolean;
9
+ }>;
10
+ }
@@ -0,0 +1,17 @@
1
+ import { post } from "../http.js";
2
+ /**
3
+ * Report an error an app saw, into the platform's one error log (the same BigQuery error-events table the
4
+ * server writes to). Best effort: never throws into the flow that failed.
5
+ */
6
+ export var errors;
7
+ (function (errors) {
8
+ async function report(event) {
9
+ try {
10
+ return await post(`/admin/log/error`, event);
11
+ }
12
+ catch (_a) {
13
+ return { kept: false };
14
+ }
15
+ }
16
+ errors.report = report;
17
+ })(errors || (errors = {}));
@@ -11,6 +11,7 @@ export { form } from "./form.js";
11
11
  export { authKit } from "./authKit.js";
12
12
  export { claimSet } from "./claimSet.js";
13
13
  export { partner } from "./partner.js";
14
+ export { errors } from "./errors.js";
14
15
  export { crate } from "./crate.js";
15
16
  export { batch } from "./batch.js";
16
17
  export { variant } from "./variant.js";
package/dist/api/index.js CHANGED
@@ -13,6 +13,7 @@ export { form } from "./form.js";
13
13
  export { authKit } from "./authKit.js";
14
14
  export { claimSet } from "./claimSet.js";
15
15
  export { partner } from "./partner.js";
16
+ export { errors } from "./errors.js";
16
17
  export { crate } from "./crate.js";
17
18
  export { batch } from "./batch.js";
18
19
  export { variant } from "./variant.js";
@@ -1,4 +1,4 @@
1
- import { Partner, PartnerClaimSet, CreatePartnerClaimSetRequest, PartnerTagHit } from "../types/index.js";
1
+ import { Partner, PartnerClaimSet, CreatePartnerClaimSetRequest, PartnerTagHit, ClientErrorEvent } from "../types/index.js";
2
2
  /**
3
3
  * Partners: organisations above collections (FanConnect, Zenith Coins, …) that own NFC claim sets and
4
4
  * assign their tags into collections their people administer. Partners and their members are set up by
@@ -21,4 +21,10 @@ export declare namespace partner {
21
21
  uid?: string;
22
22
  code?: string;
23
23
  }): Promise<PartnerTagHit[]>;
24
+ /**
25
+ * What phones reported about one chip (errors.report with entityType 'tag'), newest first: a tag that
26
+ * failed to program or register, with the host's NFC log in context. For a chip in the partner's sets,
27
+ * or attempts that named one of them.
28
+ */
29
+ function tagAttempts(partnerId: string, uid: string): Promise<ClientErrorEvent[]>;
24
30
  }
@@ -42,4 +42,13 @@ export var partner;
42
42
  return request(`/admin/partner/${encodeURIComponent(partnerId)}/tags/lookup?${q}`);
43
43
  }
44
44
  partner.lookupTags = lookupTags;
45
+ /**
46
+ * What phones reported about one chip (errors.report with entityType 'tag'), newest first: a tag that
47
+ * failed to program or register, with the host's NFC log in context. For a chip in the partner's sets,
48
+ * or attempts that named one of them.
49
+ */
50
+ async function tagAttempts(partnerId, uid) {
51
+ return request(`/admin/partner/${encodeURIComponent(partnerId)}/tags/attempts?uid=${encodeURIComponent(uid)}`);
52
+ }
53
+ partner.tagAttempts = tagAttempts;
45
54
  })(partner || (partner = {}));
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.16 | Generated: 2026-11-10T17:19:48.505Z
3
+ Version: 2.1.17 | Generated: 2026-11-11T09:04:21.796Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -144,6 +144,7 @@ The Smartlinks SDK is organized into the following namespaces:
144
144
  - **bookings** - Functions for bookings operations
145
145
  - **config** - Functions for config operations
146
146
  - **containers** - Functions for containers operations
147
+ - **errors** - Functions for errors operations
147
148
  - **facets** - Functions for facets operations
148
149
  - **functions** - Functions for functions operations
149
150
  - **http** - Functions for http operations
@@ -2838,11 +2839,46 @@ interface ServerFunctionBookings {
2838
2839
  }
2839
2840
  ```
2840
2841
 
2842
+ **AppHistoryEvent** (interface)
2843
+ ```typescript
2844
+ interface AppHistoryEvent {
2845
+ id: string;
2846
+ at: string;
2847
+ type: string;
2848
+ entityId: string | null;
2849
+ data: Record<string, any>;
2850
+ actorType: 'user' | 'contact' | null;
2851
+ actorId: string | null;
2852
+ }
2853
+ ```
2854
+
2855
+ **AppHistoryQuery** (interface)
2856
+ ```typescript
2857
+ interface AppHistoryQuery {
2858
+ types?: string | string[];
2859
+ entityId?: string;
2860
+ data?: Record<string, string | number | boolean | Array<string | number | boolean>>;
2861
+ from?: string;
2862
+ to?: string;
2863
+ limit?: number;
2864
+ }
2865
+ ```
2866
+
2841
2867
  **ServerFunctionSl** (interface)
2842
2868
  ```typescript
2843
2869
  interface ServerFunctionSl {
2844
2870
  bookings: ServerFunctionBookings;
2845
- appRecords: { claim(fields: Record<string, any>): Promise<AppRecordClaimResult>; [method: string]: any };
2871
+ appRecords: {
2872
+ claim(fields: Record<string, any>): Promise<AppRecordClaimResult>;
2873
+ adjust(recordId: string, by: Record<string, number>, opts?: { atLeast?: Record<string, number>; atMost?: Record<string, number> }): Promise<any>;
2874
+ aggregate(params?: import('./appObjects').AggregateRequest): Promise<import('./appObjects').AggregateResponse & Record<string, unknown>>;
2875
+ [method: string]: any;
2876
+ };
2877
+ transaction(ops: RecordTransactionOp[], opts?: { idempotencyKey?: string }): Promise<{ results: any[]; replayed: boolean }>;
2878
+ history: {
2879
+ log(type: string, data?: Record<string, any>, opts?: { entityId?: string }): Promise<{ id: string }>;
2880
+ query(params?: AppHistoryQuery): Promise<AppHistoryEvent[]>;
2881
+ };
2846
2882
  counters: ServerFunctionCounters;
2847
2883
  tags: { resolve(id: string): Promise<ResolvedTag | null> };
2848
2884
  contacts: {
@@ -3298,6 +3334,8 @@ interface GetCollectionWidgetsOptions {
3298
3334
 
3299
3335
  **AppFunctionAuthority** = `'caller' | 'collection'`
3300
3336
 
3337
+ **RecordTransactionOp** = ``
3338
+
3301
3339
  ### appObjects
3302
3340
 
3303
3341
  **PaginatedResponse<T>** (interface)
@@ -3324,9 +3362,12 @@ interface AggregateRequest {
3324
3362
  created_at?: { gte?: string; lte?: string }
3325
3363
  closed_at?: '__notnull__' | { gte?: string; lte?: string } // cases only
3326
3364
  expires_at?: { lte?: string } // records only
3365
+ updated_at?: { gte?: string; lte?: string } // records only
3366
+ data?: Record<string, string | number | boolean | Array<string | number | boolean>>
3367
+ dataRange?: Record<string, { gte?: number; gt?: number; lte?: number; lt?: number }>
3327
3368
  }
3328
- groupBy?: string[] // see per-resource allowed values
3329
- metrics?: string[] // see per-resource allowed values below
3369
+ groupBy?: string[]
3370
+ metrics?: string[]
3330
3371
  timeSeriesField?: string // e.g. 'created_at'
3331
3372
  timeSeriesInterval?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
3332
3373
  }
@@ -3335,7 +3376,7 @@ interface AggregateRequest {
3335
3376
  **AggregateResponse** (interface)
3336
3377
  ```typescript
3337
3378
  interface AggregateResponse {
3338
- groups?: ({ count: number } & Record<string, unknown>)[]
3379
+ groups?: ({ count?: number } & Record<string, unknown>)[]
3339
3380
  timeSeries?: ({ bucket: string; count: number } & Record<string, unknown>)[]
3340
3381
  count?: number
3341
3382
  avg_close_time_seconds?: number
@@ -5966,6 +6007,7 @@ interface PartnerTagHit {
5966
6007
  set: PartnerClaimSet | null
5967
6008
  tag: ClaimSetRangeTag & { url: string | null }
5968
6009
  range: ClaimSetRange | null
6010
+ group: ClaimSetRange | null
5969
6011
  }
5970
6012
  ```
5971
6013
 
@@ -5993,6 +6035,48 @@ type ClaimSetScope = { collectionId: string }
5993
6035
 
5994
6036
  **ClaimSetImportResult** = ``
5995
6037
 
6038
+ ### clientLog
6039
+
6040
+ **ClientErrorReport** (interface)
6041
+ ```typescript
6042
+ interface ClientErrorReport {
6043
+ app?: string
6044
+ appVersion?: string
6045
+ collectionId?: string
6046
+ operation?: string
6047
+ severity?: 'debug' | 'info' | 'warning' | 'error' | 'critical'
6048
+ errorCode?: string
6049
+ errorType?: string
6050
+ message?: string
6051
+ stack?: string
6052
+ entityType?: string
6053
+ entityId?: string
6054
+ labels?: Record<string, unknown>
6055
+ context?: Record<string, unknown>
6056
+ refId?: string
6057
+ correlationId?: string
6058
+ }
6059
+ ```
6060
+
6061
+ **ClientErrorEvent** (interface)
6062
+ ```typescript
6063
+ interface ClientErrorEvent {
6064
+ id: string
6065
+ time: string
6066
+ event_category: string
6067
+ event_action: string | null
6068
+ log_level: string
6069
+ error_code: string | null
6070
+ error_message: string | null
6071
+ service_name: string | null
6072
+ service_version: string | null
6073
+ collection_id: string | null
6074
+ user_id: string | null
6075
+ labels: Record<string, unknown> | null
6076
+ context: Record<string, unknown> | null
6077
+ }
6078
+ ```
6079
+
5996
6080
  ### collection
5997
6081
 
5998
6082
  **Collection** (interface)
@@ -12830,6 +12914,10 @@ Update a crate for a collection (admin only). ```typescript const updated = awai
12830
12914
  crateId: string) → `Promise<DeleteCrateResponse>`
12831
12915
  Delete a crate for a collection (admin only). This performs a soft delete. ```typescript await crate.remove('coll_123', 'crate_abc123') ```
12832
12916
 
12917
+ ### errors
12918
+
12919
+ **report**(event: ClientErrorReport) → `Promise<`
12920
+
12833
12921
  ### facets
12834
12922
 
12835
12923
  **list**(collectionId: string,
@@ -13462,6 +13550,9 @@ A new claim set for the partner, on one of its tap domains (owners and admins).
13462
13550
  **lookupTags**(partnerId: string, query: { uid?: string; code?: string }) → `Promise<PartnerTagHit[]>`
13463
13551
  Find a tag across the partner's sets by its chip UID (8 or 14 hex) or its code (printed or bare). Never returns keys.
13464
13552
 
13553
+ **tagAttempts**(partnerId: string, uid: string) → `Promise<ClientErrorEvent[]>`
13554
+ 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. For a chip in the partner's sets, or attempts that named one of them.
13555
+
13465
13556
  ### podcast
13466
13557
 
13467
13558
  **generate**(collectionId: string,
@@ -807,6 +807,29 @@ await app.records.upsert(collectionId, appId, {
807
807
  // customId is stored for later reverse-lookup via ?customId=&sourceSystem=
808
808
  ```
809
809
 
810
+ ### Totals over data (sum, average, min, max)
811
+
812
+ Records also total the numbers in their `data`, grouped by top-level data keys, with data filters and
813
+ numeric ranges. Stock per location, forecast per SKU:
814
+
815
+ ```typescript
816
+ const stock = await app.records.aggregate(collectionId, appId, {
817
+ groupBy: ['data.location'], // up to 5: columns or 'data.<key>'
818
+ metrics: ['count', 'sum:qty', 'min:qty'], // count; sum: / avg: / min: / max: + a data key
819
+ filters: {
820
+ record_type: 'bin',
821
+ data: { warehouse: ['north', 'south'] }, // exact match, one value or any of several
822
+ dataRange: { qty: { gt: 0 } }, // gte / gt / lte / lt, numbers only
823
+ updated_at: { gte: '2026-11-01T00:00:00Z' },
824
+ },
825
+ }, true)
826
+ // stock.groups → [{ 'data.location': 'A1', count: 12, sum_qty: 340, min_qty: 2 }, …]
827
+ ```
828
+
829
+ Values that aren't numbers are left out of sums and averages. To CHANGE numbers safely (stock moves,
830
+ counts), use a server function: `ctx.sl.appRecords.adjust` and `ctx.sl.transaction`
831
+ ([server-functions.md](server-functions.md)).
832
+
810
833
  ### Counts by Record Type
811
834
 
812
835
  `aggregate()` returns counts grouped by `record_type` in a single round-trip — no separate endpoint needed:
@@ -514,6 +514,10 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
514
514
  | Read ANOTHER app's data (as the caller may see it) | `ctx.sl.app('other-app').data.get()` — caller-authority, this collection, public-filtered, read-only | `sl:data:read` |
515
515
  | Guarantee a UNIQUE claim (one entry per tag, one-per-user, idempotency key, a number nobody else holds) | `ctx.sl.appRecords.claim({ …, singletonPer, customId })` → `{ record, created, conflict? }` — see below | `sl:records:write` |
516
516
  | Hand out the next number (tickets, order numbers) | `ctx.sl.counters.next('tickets')` — atomic, distinct per caller | `sl:data:write` |
517
+ | Change numbers in a record (stock, counts, balances) without losing updates | `ctx.sl.appRecords.adjust(id, { qty: -5 }, { atLeast: { qty: 0 } })` — in place, conditions checked after the change | `sl:records:write` |
518
+ | Several record writes that land together or not at all (a stock move) | `ctx.sl.transaction([ops], { idempotencyKey })` — see below | `sl:records:write` |
519
+ | Totals: stock per location, forecast per SKU | `ctx.sl.appRecords.aggregate({ groupBy: ['data.location'], metrics: ['sum:qty'] })` | `sl:records:read` |
520
+ | Keep a history of what happened (stock moves, counts) without growing your records | `ctx.sl.history.log('stock.moved', {…})`, or a `log` op in a transaction; read with `ctx.sl.history.query({…})` | `sl:history:write` / `sl:history:read` |
517
521
  | Check a tapped tag is real | `ctx.sl.tags.resolve(proofId)` → `{ id, claimSetId, code }` or `null` | `sl:tags:read` |
518
522
  | Put a visitor's details on a contact | `ctx.sl.contacts.upsert({ email, firstName, … })` → `{ contactId, created }` (fills empty fields only) | `sl:contacts:write` |
519
523
  | Record "they did X" | `ctx.sl.interactions.record({ interactionId, contactId, outcome, metadata })` | `sl:interactions:write` |
@@ -579,6 +583,90 @@ reads the last value handed out (`null` before the first). Name a new counter to
579
583
  `customId: String(n)`. If the claim says `conflict: 'customId'`, take `next()` again. A failed or raced
580
584
  attempt only leaves a gap — never a duplicate.
581
585
 
586
+ ### `appRecords.adjust(id, by, { atLeast?, atMost? })` — numbers that never lose an update
587
+
588
+ Adds to numbers in a record's `data` in ONE statement, so twenty scanners taking from the same bin at
589
+ once each land: no read-then-write, no lost counts. A missing value counts as 0. `atLeast` / `atMost` are
590
+ checked on the values **after** the change, inside the same statement:
591
+
592
+ ```ts
593
+ // Take 5 from bin A — refused (nothing changes) if it would go below zero.
594
+ await ctx.sl.appRecords.adjust(binA, { qty: -5 }, { atLeast: { qty: 0 } })
595
+ ```
596
+
597
+ Refusals carry `code`: `NOT_FOUND` (not this app's record in this collection, or deleted), `NOT_A_NUMBER`
598
+ (the key holds something else), `CONDITION_FAILED` (409, with the `current` values). Only top-level data
599
+ keys; a key named only in a condition is checked as it stands.
600
+
601
+ ### `transaction(ops, { idempotencyKey? })` — all or nothing
602
+
603
+ Several writes as ONE transaction: every op lands, or none does. The classic: take stock out of bin A,
604
+ put it on order line B, and log the move.
605
+
606
+ ```ts
607
+ const { results, replayed } = await ctx.sl.transaction([
608
+ { op: 'adjust', recordId: binA, by: { qty: -5 }, atLeast: { qty: 0 } },
609
+ { op: 'adjust', recordId: orderLineB, by: { picked: 5 } },
610
+ { op: 'create', record: { recordType: 'movement', data: { from: binA, to: orderLineB, qty: 5 } } },
611
+ ], { idempotencyKey: scanId })
612
+ ```
613
+
614
+ - Ops: `create` (`record`), `update` (`recordId`, `fields`), `adjust` (`recordId`, `by`, `atLeast?`,
615
+ `atMost?`), `delete` (`recordId`), `log` (`type`, `data`, `entityId?`: a history event, see below).
616
+ Up to 50, run in order. This app's records in this collection only.
617
+ - A failing op rolls everything back. The error says which op (`opIndex`) and why (its `code`).
618
+ - Change events and realtime updates go out only after the transaction commits, never for a rollback.
619
+ - **`idempotencyKey`** ("this was already done"): recorded in the same commit, with what was written
620
+ (ids only). The same key again within 7 days doesn't write; it returns those records as they are now,
621
+ with `replayed: true`. So a handheld that retries after a dropped signal never moves stock twice. Two
622
+ calls with one key at the same moment: one commits, the other replays it. A transaction that failed
623
+ leaves its key free, so a retry can still succeed.
624
+ - Use the scan's or the request's own id as the key (one key = one intended change).
625
+ - Browser code never writes stock directly: a warehouse screen calls your function.
626
+
627
+ ### `history` — what happened, kept without growing your records
628
+
629
+ Keep **current state** in records (a bin's quantity, an order's status) and **what happened** in the app's
630
+ history: append-only events in SmartLinks' analytics store, kept for good, cheap however many there
631
+ are. Picking every order line for years would bloat records; as history it costs nothing operational.
632
+
633
+ ```ts
634
+ // In a transaction: commits with the stock change, or not at all.
635
+ await ctx.sl.transaction([
636
+ { op: 'adjust', recordId: binA, by: { qty: -5 }, atLeast: { qty: 0 } },
637
+ { op: 'log', type: 'stock.moved', entityId: binA, data: { sku: 'EDP-50', from: 'A1', to: orderId, qty: 5 } },
638
+ ], { idempotencyKey: scanId })
639
+
640
+ // On its own:
641
+ await ctx.sl.history.log('count.taken', { bin: 'A1', counted: 40, expected: 42 }, { entityId: binA })
642
+
643
+ // Reading back: this app's events in this collection, newest first (default last 30 days, max 400).
644
+ const moves = await ctx.sl.history.query({ types: 'stock.moved', entityId: binA, limit: 20 })
645
+ // → [{ id, at, type, entityId, data, actorType, actorId }]
646
+ ```
647
+
648
+ - `type`: lowercase letters, digits and `. _ -` (`stock.moved`). `data`: up to 32 KB, so **log a summary**:
649
+ one line for "5 of EDP-50", not five; one line for an RFID count, not one per tag.
650
+ - Delivery takes seconds and is retried until it lands; a logged event is never lost. A query reads the
651
+ analytics store, so expect about a second: right for a history panel, not for a hot path.
652
+ - The person who called your function is recorded as the actor.
653
+
654
+ ### `appRecords.aggregate(params)` — totals
655
+
656
+ Counts and sums over your app's records, worked out in the database:
657
+
658
+ ```ts
659
+ await ctx.sl.appRecords.aggregate({
660
+ groupBy: ['data.location', 'data.sku'], // columns, or top-level data keys
661
+ metrics: ['count', 'sum:qty'], // sum: / avg: / min: / max: over data numbers
662
+ filters: { record_type: 'bin', data: { warehouse: 'north' }, dataRange: { qty: { gt: 0 } } },
663
+ })
664
+ // → { groups: [{ 'data.location': 'A1', 'data.sku': 'EDP-50', count: 3, sum_qty: 42 }, …] }
665
+ ```
666
+
667
+ Non-numbers are ignored by `sum`/`avg`/`min`/`max`. The same request works from the browser through
668
+ `app.records.aggregate` (admin) — see [app-objects.md](app-objects.md).
669
+
582
670
  ### `tags.resolve(id)` — is this tap real?
583
671
 
584
672
  A tap hands your app the virtual-proof id `<claimSetId>-<code>`. `resolve` returns
@@ -76,6 +76,14 @@ has a `kind`:
76
76
 
77
77
  Status reads `open` or `group` for these (and `assigned` once every member of a group is assigned).
78
78
 
79
+ ## When a tag fails on the phone
80
+
81
+ `errors.report({ app: 'sidekick', operation: 'tag.program', errorCode: 'TAG_LOST', message, entityType: 'tag',
82
+ entityId: chipUid, labels: { claimSetId, code, failedStep }, context: { steps, timings, hostLog } })` keeps what
83
+ the phone saw in the platform's one error log (never a key: fields that look like one are dropped).
84
+ `partner.tagAttempts(partner.id, chipUid)` and the platform's tag lookup read it back, newest first, so a
85
+ failing chip can be understood by its UID without the phone's console.
86
+
79
87
  ## The printer file
80
88
 
81
89
  One row per tag, in index order. The manufacturer fills in `UID` and sends the same file back; rows are
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:
@@ -20828,6 +20857,56 @@ components:
20828
20857
  required:
20829
20858
  - slots
20830
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
20831
20910
  ServerFunctionSl:
20832
20911
  type: object
20833
20912
  properties:
@@ -20836,6 +20915,9 @@ components:
20836
20915
  appRecords:
20837
20916
  type: object
20838
20917
  additionalProperties: true
20918
+ history:
20919
+ type: object
20920
+ additionalProperties: true
20839
20921
  counters:
20840
20922
  $ref: "#/components/schemas/ServerFunctionCounters"
20841
20923
  tags:
@@ -20857,6 +20939,7 @@ components:
20857
20939
  required:
20858
20940
  - bookings
20859
20941
  - appRecords
20942
+ - history
20860
20943
  - counters
20861
20944
  - tags
20862
20945
  - contacts
@@ -21632,6 +21715,9 @@ components:
21632
21715
  enum:
21633
21716
  - caller
21634
21717
  - collection
21718
+ RecordTransactionOp:
21719
+ type: object
21720
+ additionalProperties: true
21635
21721
  PaginatedResponse:
21636
21722
  type: object
21637
21723
  properties:
@@ -21681,6 +21767,19 @@ components:
21681
21767
  expires_at:
21682
21768
  type: object
21683
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
21684
21783
  groupBy:
21685
21784
  type: array
21686
21785
  items:
@@ -26381,12 +26480,15 @@ components:
26381
26480
  $ref: "#/components/schemas/ClaimSetRangeTag"
26382
26481
  range:
26383
26482
  $ref: "#/components/schemas/ClaimSetRange"
26483
+ group:
26484
+ $ref: "#/components/schemas/ClaimSetRange"
26384
26485
  required:
26385
26486
  - namespace
26386
26487
  - claimSetId
26387
26488
  - set
26388
26489
  - tag
26389
26490
  - range
26491
+ - group
26390
26492
  CreatePartnerClaimSetRequest:
26391
26493
  type: object
26392
26494
  properties:
@@ -26444,6 +26546,92 @@ components:
26444
26546
  - duplicate_uid
26445
26547
  - uid_in_use
26446
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
26447
26635
  Collection:
26448
26636
  type: object
26449
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;