@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
package/docs/server-functions.md
CHANGED
|
@@ -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/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/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:
|