@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.
- package/dist/api/errors.d.ts +10 -0
- package/dist/api/errors.js +17 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/partner.d.ts +7 -1
- package/dist/api/partner.js +9 -0
- package/dist/docs/API_SUMMARY.md +96 -5
- package/dist/docs/app-objects.md +23 -0
- package/dist/docs/server-functions.md +88 -0
- package/dist/docs/tag-ranges.md +8 -0
- package/dist/openapi.yaml +188 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.js +123 -1
- package/dist/types/appManifest.d.ts +87 -0
- package/dist/types/appObjects.d.ts +22 -1
- package/dist/types/claimSet.d.ts +2 -0
- package/dist/types/clientLog.d.ts +46 -0
- package/dist/types/clientLog.js +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/docs/API_SUMMARY.md +96 -5
- package/docs/app-objects.md +23 -0
- package/docs/server-functions.md +88 -0
- package/docs/tag-ranges.md +8 -0
- package/openapi.yaml +188 -0
- package/package.json +1 -1
|
@@ -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 = {}));
|
package/dist/api/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/api/partner.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/api/partner.js
CHANGED
|
@@ -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 = {}));
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.1.
|
|
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: {
|
|
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[]
|
|
3329
|
-
metrics?: string[]
|
|
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
|
|
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,
|
package/dist/docs/app-objects.md
CHANGED
|
@@ -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
|
package/dist/docs/tag-ranges.md
CHANGED
|
@@ -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:
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -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;
|