@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/api/claimSet.d.ts +9 -1
- package/dist/api/claimSet.js +15 -0
- 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 +140 -9
- package/dist/docs/app-objects.md +23 -0
- package/dist/docs/server-functions.md +88 -0
- package/dist/docs/tag-ranges.md +29 -0
- package/dist/openapi.yaml +309 -1
- 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 +61 -8
- 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 +140 -9
- package/docs/app-objects.md +23 -0
- package/docs/server-functions.md +88 -0
- package/docs/tag-ranges.md +29 -0
- package/openapi.yaml +309 -1
- package/package.json +1 -1
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:
|
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;
|
package/dist/testing/index.js
CHANGED
|
@@ -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'),
|