@proveanything/smartlinks 1.9.22 → 1.9.23
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/appObjects.d.ts +67 -1
- package/dist/api/appObjects.js +88 -8
- package/dist/docs/API_SUMMARY.md +160 -6
- package/dist/docs/app-objects.md +147 -2
- package/dist/openapi.yaml +430 -0
- package/dist/types/appObjects.d.ts +190 -2
- package/docs/API_SUMMARY.md +160 -6
- package/docs/app-objects.md +147 -2
- package/openapi.yaml +430 -0
- package/package.json +1 -1
|
@@ -284,6 +284,150 @@ export interface ThreadListQueryParams extends ListQueryParams {
|
|
|
284
284
|
tag?: string;
|
|
285
285
|
contactId?: string;
|
|
286
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Facet clause within a RecordScope.
|
|
289
|
+
* Values within a single clause are ORed; multiple clauses are ANDed.
|
|
290
|
+
*/
|
|
291
|
+
export interface ScopeFacetClause {
|
|
292
|
+
/** Facet key, e.g. "tier", "region" */
|
|
293
|
+
key: string;
|
|
294
|
+
/** One or more values that satisfy this clause (OR semantics) */
|
|
295
|
+
valueKeys: string[];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Structured scope definition for a record.
|
|
299
|
+
* Describes the audience/context the record applies to.
|
|
300
|
+
* An empty object `{}` means universal (no restrictions).
|
|
301
|
+
*/
|
|
302
|
+
export interface RecordScope {
|
|
303
|
+
productId?: string;
|
|
304
|
+
variantId?: string;
|
|
305
|
+
proofId?: string;
|
|
306
|
+
batchId?: string;
|
|
307
|
+
/**
|
|
308
|
+
* Arbitrary facet clauses.
|
|
309
|
+
* Clauses are ANDed together; valueKeys within a clause are ORed.
|
|
310
|
+
*/
|
|
311
|
+
facets?: ScopeFacetClause[];
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Runtime context passed to the match endpoint.
|
|
315
|
+
* Describes the caller or item being evaluated against record scopes.
|
|
316
|
+
*/
|
|
317
|
+
export interface RecordTarget {
|
|
318
|
+
productId?: string;
|
|
319
|
+
variantId?: string;
|
|
320
|
+
proofId?: string;
|
|
321
|
+
batchId?: string;
|
|
322
|
+
/**
|
|
323
|
+
* Facet values the caller possesses, keyed by facet key.
|
|
324
|
+
* A scope clause is satisfied if ANY of the clause's valueKeys appears here.
|
|
325
|
+
*/
|
|
326
|
+
facets?: Record<string, string[]>;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Request body for the bulk-upsert endpoint.
|
|
330
|
+
*/
|
|
331
|
+
export interface BulkUpsertItem {
|
|
332
|
+
/** Required — logical identifier used as the upsert key */
|
|
333
|
+
ref: string;
|
|
334
|
+
recordType?: string;
|
|
335
|
+
customId?: string;
|
|
336
|
+
sourceSystem?: string;
|
|
337
|
+
startsAt?: string | null;
|
|
338
|
+
expiresAt?: string | null;
|
|
339
|
+
status?: string | null;
|
|
340
|
+
scope?: RecordScope;
|
|
341
|
+
data?: Record<string, unknown> | null;
|
|
342
|
+
metadata?: Record<string, unknown> | null;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Response from the bulk-upsert endpoint.
|
|
346
|
+
*/
|
|
347
|
+
export interface BulkUpsertResult {
|
|
348
|
+
saved: number;
|
|
349
|
+
failed: number;
|
|
350
|
+
results: Array<{
|
|
351
|
+
index: number;
|
|
352
|
+
status: 'created';
|
|
353
|
+
id: string;
|
|
354
|
+
ref: string;
|
|
355
|
+
created: true;
|
|
356
|
+
} | {
|
|
357
|
+
index: number;
|
|
358
|
+
status: 'updated';
|
|
359
|
+
id: string;
|
|
360
|
+
ref: string;
|
|
361
|
+
created: false;
|
|
362
|
+
} | {
|
|
363
|
+
index: number;
|
|
364
|
+
status: 'error';
|
|
365
|
+
error: string;
|
|
366
|
+
}>;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Response from the bulk-delete endpoint.
|
|
370
|
+
*/
|
|
371
|
+
export interface BulkDeleteResult {
|
|
372
|
+
deleted: number;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Response from the match endpoint.
|
|
376
|
+
*/
|
|
377
|
+
export interface MatchResult {
|
|
378
|
+
/** Matched records ordered by specificity descending (most specific first) */
|
|
379
|
+
records: AppRecord[];
|
|
380
|
+
/**
|
|
381
|
+
* Only present when strategy is 'best'.
|
|
382
|
+
* The single highest-specificity record per recordType.
|
|
383
|
+
*/
|
|
384
|
+
best?: Record<string, AppRecord>;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Request body for the upsert endpoint.
|
|
388
|
+
*/
|
|
389
|
+
export interface UpsertRecordInput {
|
|
390
|
+
/** Required — used as the lookup key */
|
|
391
|
+
ref: string;
|
|
392
|
+
recordType?: string;
|
|
393
|
+
customId?: string;
|
|
394
|
+
sourceSystem?: string;
|
|
395
|
+
startsAt?: string | null;
|
|
396
|
+
expiresAt?: string | null;
|
|
397
|
+
status?: string | null;
|
|
398
|
+
scope?: RecordScope;
|
|
399
|
+
data?: Record<string, unknown> | null;
|
|
400
|
+
metadata?: Record<string, unknown> | null;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Response from the upsert endpoint — includes AppRecord plus a created flag.
|
|
404
|
+
*/
|
|
405
|
+
export interface UpsertRecordResponse extends AppRecord {
|
|
406
|
+
/** true if the record was newly created, false if updated */
|
|
407
|
+
created: boolean;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Request body for the match endpoint.
|
|
411
|
+
*/
|
|
412
|
+
export interface MatchRecordsInput {
|
|
413
|
+
/** Required — describes the runtime context to match against */
|
|
414
|
+
target: RecordTarget;
|
|
415
|
+
/**
|
|
416
|
+
* 'all' — return all matching records (default)
|
|
417
|
+
* 'best' — return the highest-specificity record per recordType
|
|
418
|
+
*/
|
|
419
|
+
strategy?: 'all' | 'best';
|
|
420
|
+
/** Limit to a specific recordType */
|
|
421
|
+
recordType?: string;
|
|
422
|
+
/** Maximum records to return. Default 100, max 1000. */
|
|
423
|
+
limit?: number;
|
|
424
|
+
/** Include records whose startsAt is in the future. Default false. */
|
|
425
|
+
includeScheduled?: boolean;
|
|
426
|
+
/** Include records whose expiresAt is in the past. Default false. */
|
|
427
|
+
includeExpired?: boolean;
|
|
428
|
+
/** Evaluate scheduling relative to this ISO 8601 timestamp. Defaults to now. */
|
|
429
|
+
at?: string;
|
|
430
|
+
}
|
|
287
431
|
/**
|
|
288
432
|
* App Record object
|
|
289
433
|
*/
|
|
@@ -293,9 +437,11 @@ export interface AppRecord {
|
|
|
293
437
|
collectionId: string;
|
|
294
438
|
appId: string;
|
|
295
439
|
visibility: Visibility;
|
|
296
|
-
recordType: string;
|
|
440
|
+
recordType: string | null;
|
|
297
441
|
ref: string | null;
|
|
298
|
-
|
|
442
|
+
customId: string | null;
|
|
443
|
+
sourceSystem: string | null;
|
|
444
|
+
status: string | null;
|
|
299
445
|
productId: string | null;
|
|
300
446
|
proofId: string | null;
|
|
301
447
|
contactId: string | null;
|
|
@@ -308,9 +454,20 @@ export interface AppRecord {
|
|
|
308
454
|
startsAt: string | null;
|
|
309
455
|
expiresAt: string | null;
|
|
310
456
|
deletedAt: string | null;
|
|
457
|
+
/**
|
|
458
|
+
* Structured scope definition. Empty object means universal.
|
|
459
|
+
* Platform-canonicalized on write (keys sorted, valueKeys deduplicated).
|
|
460
|
+
*/
|
|
461
|
+
scope: RecordScope;
|
|
462
|
+
/**
|
|
463
|
+
* Numeric specificity score computed from scope.
|
|
464
|
+
* Higher = more specific. 0 = universal scope.
|
|
465
|
+
*/
|
|
466
|
+
specificity: number;
|
|
311
467
|
data: Record<string, unknown>;
|
|
312
468
|
owner: Record<string, unknown>;
|
|
313
469
|
admin: Record<string, unknown>;
|
|
470
|
+
metadata: Record<string, unknown> | null;
|
|
314
471
|
}
|
|
315
472
|
/**
|
|
316
473
|
* Input for creating a new record
|
|
@@ -329,9 +486,14 @@ export interface CreateRecordInput {
|
|
|
329
486
|
parentId?: string;
|
|
330
487
|
startsAt?: string;
|
|
331
488
|
expiresAt?: string;
|
|
489
|
+
/** Structured scope. Canonicalized on write; ref derived if not supplied. */
|
|
490
|
+
scope?: RecordScope;
|
|
491
|
+
customId?: string;
|
|
492
|
+
sourceSystem?: string;
|
|
332
493
|
data?: Record<string, unknown>;
|
|
333
494
|
owner?: Record<string, unknown>;
|
|
334
495
|
admin?: Record<string, unknown>;
|
|
496
|
+
metadata?: Record<string, unknown>;
|
|
335
497
|
}
|
|
336
498
|
/**
|
|
337
499
|
* Input for updating a record
|
|
@@ -346,6 +508,11 @@ export interface UpdateRecordInput {
|
|
|
346
508
|
recordType?: string;
|
|
347
509
|
startsAt?: string;
|
|
348
510
|
expiresAt?: string;
|
|
511
|
+
/** Updating scope recomputes specificity and ref. */
|
|
512
|
+
scope?: RecordScope;
|
|
513
|
+
customId?: string;
|
|
514
|
+
sourceSystem?: string;
|
|
515
|
+
metadata?: Record<string, unknown>;
|
|
349
516
|
}
|
|
350
517
|
/**
|
|
351
518
|
* Query parameters for listing records
|
|
@@ -353,12 +520,33 @@ export interface UpdateRecordInput {
|
|
|
353
520
|
export interface RecordListQueryParams extends ListQueryParams {
|
|
354
521
|
recordType?: string;
|
|
355
522
|
ref?: string;
|
|
523
|
+
/** Filter records whose ref starts with this value */
|
|
524
|
+
refPrefix?: string;
|
|
525
|
+
customId?: string;
|
|
526
|
+
sourceSystem?: string;
|
|
527
|
+
status?: string;
|
|
356
528
|
proofId?: string;
|
|
529
|
+
productId?: string;
|
|
530
|
+
/** Filter by scope.variantId (JSONB lookup) */
|
|
531
|
+
variantId?: string;
|
|
532
|
+
/** Filter by scope.batchId (JSONB lookup) */
|
|
533
|
+
batchId?: string;
|
|
534
|
+
/** Full-text filter on data.label (case-insensitive substring) */
|
|
535
|
+
q?: string;
|
|
357
536
|
authorId?: string;
|
|
358
537
|
parentType?: string;
|
|
359
538
|
parentId?: string;
|
|
360
539
|
startsAt?: string;
|
|
361
540
|
expiresAt?: string;
|
|
541
|
+
/** Include records where startsAt is in the future. Default false. */
|
|
542
|
+
includeScheduled?: boolean;
|
|
543
|
+
/** Include records where expiresAt is in the past. Default false. */
|
|
544
|
+
includeExpired?: boolean;
|
|
545
|
+
/**
|
|
546
|
+
* Evaluate scheduling relative to this ISO 8601 timestamp.
|
|
547
|
+
* Defaults to now.
|
|
548
|
+
*/
|
|
549
|
+
at?: string;
|
|
362
550
|
contactId?: string;
|
|
363
551
|
}
|
|
364
552
|
/**
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.23 | Generated: 2026-04-25T12:36:26.919Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -1880,6 +1880,117 @@ interface ReplyInput {
|
|
|
1880
1880
|
}
|
|
1881
1881
|
```
|
|
1882
1882
|
|
|
1883
|
+
**ScopeFacetClause** (interface)
|
|
1884
|
+
```typescript
|
|
1885
|
+
interface ScopeFacetClause {
|
|
1886
|
+
key: string
|
|
1887
|
+
valueKeys: string[]
|
|
1888
|
+
}
|
|
1889
|
+
```
|
|
1890
|
+
|
|
1891
|
+
**RecordScope** (interface)
|
|
1892
|
+
```typescript
|
|
1893
|
+
interface RecordScope {
|
|
1894
|
+
productId?: string
|
|
1895
|
+
variantId?: string
|
|
1896
|
+
proofId?: string
|
|
1897
|
+
batchId?: string
|
|
1898
|
+
* Arbitrary facet clauses.
|
|
1899
|
+
* Clauses are ANDed together; valueKeys within a clause are ORed.
|
|
1900
|
+
facets?: ScopeFacetClause[]
|
|
1901
|
+
}
|
|
1902
|
+
```
|
|
1903
|
+
|
|
1904
|
+
**RecordTarget** (interface)
|
|
1905
|
+
```typescript
|
|
1906
|
+
interface RecordTarget {
|
|
1907
|
+
productId?: string
|
|
1908
|
+
variantId?: string
|
|
1909
|
+
proofId?: string
|
|
1910
|
+
batchId?: string
|
|
1911
|
+
* Facet values the caller possesses, keyed by facet key.
|
|
1912
|
+
* A scope clause is satisfied if ANY of the clause's valueKeys appears here.
|
|
1913
|
+
facets?: Record<string, string[]>
|
|
1914
|
+
}
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
**BulkUpsertItem** (interface)
|
|
1918
|
+
```typescript
|
|
1919
|
+
interface BulkUpsertItem {
|
|
1920
|
+
ref: string
|
|
1921
|
+
recordType?: string
|
|
1922
|
+
customId?: string
|
|
1923
|
+
sourceSystem?: string
|
|
1924
|
+
startsAt?: string | null
|
|
1925
|
+
expiresAt?: string | null
|
|
1926
|
+
status?: string | null
|
|
1927
|
+
scope?: RecordScope
|
|
1928
|
+
data?: Record<string, unknown> | null
|
|
1929
|
+
metadata?: Record<string, unknown> | null
|
|
1930
|
+
}
|
|
1931
|
+
```
|
|
1932
|
+
|
|
1933
|
+
**BulkUpsertResult** (interface)
|
|
1934
|
+
```typescript
|
|
1935
|
+
interface BulkUpsertResult {
|
|
1936
|
+
saved: number
|
|
1937
|
+
failed: number
|
|
1938
|
+
results: Array<
|
|
1939
|
+
| { index: number; status: 'created'; id: string; ref: string; created: true }
|
|
1940
|
+
| { index: number; status: 'updated'; id: string; ref: string; created: false }
|
|
1941
|
+
| { index: number; status: 'error'; error: string }
|
|
1942
|
+
>
|
|
1943
|
+
}
|
|
1944
|
+
```
|
|
1945
|
+
|
|
1946
|
+
**BulkDeleteResult** (interface)
|
|
1947
|
+
```typescript
|
|
1948
|
+
interface BulkDeleteResult {
|
|
1949
|
+
deleted: number
|
|
1950
|
+
}
|
|
1951
|
+
```
|
|
1952
|
+
|
|
1953
|
+
**MatchResult** (interface)
|
|
1954
|
+
```typescript
|
|
1955
|
+
interface MatchResult {
|
|
1956
|
+
records: AppRecord[]
|
|
1957
|
+
* Only present when strategy is 'best'.
|
|
1958
|
+
* The single highest-specificity record per recordType.
|
|
1959
|
+
best?: Record<string, AppRecord>
|
|
1960
|
+
}
|
|
1961
|
+
```
|
|
1962
|
+
|
|
1963
|
+
**UpsertRecordInput** (interface)
|
|
1964
|
+
```typescript
|
|
1965
|
+
interface UpsertRecordInput {
|
|
1966
|
+
ref: string
|
|
1967
|
+
recordType?: string
|
|
1968
|
+
customId?: string
|
|
1969
|
+
sourceSystem?: string
|
|
1970
|
+
startsAt?: string | null
|
|
1971
|
+
expiresAt?: string | null
|
|
1972
|
+
status?: string | null
|
|
1973
|
+
scope?: RecordScope
|
|
1974
|
+
data?: Record<string, unknown> | null
|
|
1975
|
+
metadata?: Record<string, unknown> | null
|
|
1976
|
+
}
|
|
1977
|
+
```
|
|
1978
|
+
|
|
1979
|
+
**MatchRecordsInput** (interface)
|
|
1980
|
+
```typescript
|
|
1981
|
+
interface MatchRecordsInput {
|
|
1982
|
+
target: RecordTarget
|
|
1983
|
+
* 'all' — return all matching records (default)
|
|
1984
|
+
* 'best' — return the highest-specificity record per recordType
|
|
1985
|
+
strategy?: 'all' | 'best'
|
|
1986
|
+
recordType?: string
|
|
1987
|
+
limit?: number
|
|
1988
|
+
includeScheduled?: boolean
|
|
1989
|
+
includeExpired?: boolean
|
|
1990
|
+
at?: string
|
|
1991
|
+
}
|
|
1992
|
+
```
|
|
1993
|
+
|
|
1883
1994
|
**AppRecord** (interface)
|
|
1884
1995
|
```typescript
|
|
1885
1996
|
interface AppRecord {
|
|
@@ -1888,11 +1999,13 @@ interface AppRecord {
|
|
|
1888
1999
|
collectionId: string
|
|
1889
2000
|
appId: string
|
|
1890
2001
|
visibility: Visibility
|
|
1891
|
-
recordType: string
|
|
2002
|
+
recordType: string | null
|
|
1892
2003
|
ref: string | null
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2004
|
+
customId: string | null
|
|
2005
|
+
sourceSystem: string | null
|
|
2006
|
+
status: string | null
|
|
2007
|
+
productId: string | null // @deprecated — use scope.productId
|
|
2008
|
+
proofId: string | null // @deprecated — use scope.proofId
|
|
1896
2009
|
contactId: string | null
|
|
1897
2010
|
authorId: string | null
|
|
1898
2011
|
authorType: string
|
|
@@ -1903,9 +2016,16 @@ interface AppRecord {
|
|
|
1903
2016
|
startsAt: string | null
|
|
1904
2017
|
expiresAt: string | null
|
|
1905
2018
|
deletedAt: string | null // admin only
|
|
2019
|
+
* Structured scope definition. Empty object means universal.
|
|
2020
|
+
* Platform-canonicalized on write (keys sorted, valueKeys deduplicated).
|
|
2021
|
+
scope: RecordScope
|
|
2022
|
+
* Numeric specificity score computed from scope.
|
|
2023
|
+
* Higher = more specific. 0 = universal scope.
|
|
2024
|
+
specificity: number
|
|
1906
2025
|
data: Record<string, unknown>
|
|
1907
2026
|
owner: Record<string, unknown>
|
|
1908
2027
|
admin: Record<string, unknown> // admin only
|
|
2028
|
+
metadata: Record<string, unknown> | null
|
|
1909
2029
|
}
|
|
1910
2030
|
```
|
|
1911
2031
|
|
|
@@ -1914,7 +2034,7 @@ interface AppRecord {
|
|
|
1914
2034
|
interface CreateRecordInput {
|
|
1915
2035
|
recordType: string
|
|
1916
2036
|
visibility?: Visibility // default 'owner'
|
|
1917
|
-
ref?: string
|
|
2037
|
+
ref?: string // derived from scope if omitted and scope provided
|
|
1918
2038
|
status?: string // default 'active'
|
|
1919
2039
|
productId?: string
|
|
1920
2040
|
proofId?: string
|
|
@@ -1925,9 +2045,13 @@ interface CreateRecordInput {
|
|
|
1925
2045
|
parentId?: string
|
|
1926
2046
|
startsAt?: string // ISO 8601
|
|
1927
2047
|
expiresAt?: string
|
|
2048
|
+
scope?: RecordScope
|
|
2049
|
+
customId?: string
|
|
2050
|
+
sourceSystem?: string
|
|
1928
2051
|
data?: Record<string, unknown>
|
|
1929
2052
|
owner?: Record<string, unknown>
|
|
1930
2053
|
admin?: Record<string, unknown> // admin only
|
|
2054
|
+
metadata?: Record<string, unknown>
|
|
1931
2055
|
}
|
|
1932
2056
|
```
|
|
1933
2057
|
|
|
@@ -1943,6 +2067,10 @@ interface UpdateRecordInput {
|
|
|
1943
2067
|
recordType?: string
|
|
1944
2068
|
startsAt?: string
|
|
1945
2069
|
expiresAt?: string
|
|
2070
|
+
scope?: RecordScope
|
|
2071
|
+
customId?: string
|
|
2072
|
+
sourceSystem?: string
|
|
2073
|
+
metadata?: Record<string, unknown>
|
|
1946
2074
|
}
|
|
1947
2075
|
```
|
|
1948
2076
|
|
|
@@ -7148,6 +7276,32 @@ Soft delete a record DELETE /records/:recordId
|
|
|
7148
7276
|
admin: boolean = false) → `Promise<AggregateResponse>`
|
|
7149
7277
|
Get aggregate statistics for records POST /records/aggregate
|
|
7150
7278
|
|
|
7279
|
+
**restore**(collectionId: string,
|
|
7280
|
+
appId: string,
|
|
7281
|
+
recordId: string) → `Promise<AppRecord>`
|
|
7282
|
+
Restore a soft-deleted record. POST /records/:recordId/restore (admin only)
|
|
7283
|
+
|
|
7284
|
+
**upsert**(collectionId: string,
|
|
7285
|
+
appId: string,
|
|
7286
|
+
input: UpsertRecordInput) → `Promise<UpsertRecordResponse>`
|
|
7287
|
+
Upsert a record by ref — creates if no record with that ref exists, otherwise updates. Scope, specificity, and ref are canonicalized on write. POST /records/upsert (admin only)
|
|
7288
|
+
|
|
7289
|
+
**match**(collectionId: string,
|
|
7290
|
+
appId: string,
|
|
7291
|
+
input: MatchRecordsInput,
|
|
7292
|
+
admin: boolean = false) → `Promise<MatchResult>`
|
|
7293
|
+
Match records against a runtime target scope. Returns records whose scope is satisfied by the target, ordered by specificity descending (most specific first). POST /records/match ```ts const { records, best } = await app.records.match(collectionId, appId, { target: { productId: 'prod_abc', facets: { tier: ['gold'] } }, strategy: 'best', recordType: 'nutrition', }, true); // best.nutrition → the single highest-specificity nutrition record ```
|
|
7294
|
+
|
|
7295
|
+
**bulkUpsert**(collectionId: string,
|
|
7296
|
+
appId: string,
|
|
7297
|
+
records: BulkUpsertItem[]) → `Promise<BulkUpsertResult>`
|
|
7298
|
+
Upsert up to 500 records in a single transaction. Each row is individually error-isolated — a failure on one row does not abort the others. POST /records/bulk-upsert (admin only)
|
|
7299
|
+
|
|
7300
|
+
**bulkDelete**(collectionId: string,
|
|
7301
|
+
appId: string,
|
|
7302
|
+
input: { refs: string[]; recordType?: string } | { scope: Omit<RecordScope, 'facets'>; recordType?: string }) → `Promise<BulkDeleteResult>`
|
|
7303
|
+
Soft-delete records in bulk. Supports two modes: - **refs mode**: explicit list of refs (max 1000) - **scope mode**: delete by scope anchor (productId / variantId / etc.) POST /records/bulk-delete (admin only) ```ts // Refs mode await app.records.bulkDelete(collectionId, appId, { refs: ['product:prod_abc', 'product:prod_xyz'], recordType: 'nutrition', }); // Scope mode await app.records.bulkDelete(collectionId, appId, { scope: { productId: 'prod_abc' }, }); ```
|
|
7304
|
+
|
|
7151
7305
|
### app.threads
|
|
7152
7306
|
|
|
7153
7307
|
Conversation-oriented app objects for comments, discussions, Q&A, and reply-driven experiences.
|
package/docs/app-objects.md
CHANGED
|
@@ -432,7 +432,7 @@ const productComments = await app.threads.list(collectionId, appId, {
|
|
|
432
432
|
|
|
433
433
|
## Records
|
|
434
434
|
|
|
435
|
-
**Records** are the most flexible object type — use them for structured data with time-based lifecycles, hierarchies, or custom schemas.
|
|
435
|
+
**Records** are the most flexible object type — use them for structured data with time-based lifecycles, hierarchies, or custom schemas. Records also support **structured scoping** — each record can declare which products, variants, batches, proofs, or facet values it applies to, and the platform can match records against a runtime context.
|
|
436
436
|
|
|
437
437
|
### When to Use Records
|
|
438
438
|
|
|
@@ -444,15 +444,160 @@ const productComments = await app.threads.list(collectionId, appId, {
|
|
|
444
444
|
- **Usage logs** — record product usage metrics
|
|
445
445
|
- **Audit trails** — immutable logs of actions
|
|
446
446
|
- **Loyalty points** — track points earned/redeemed
|
|
447
|
+
- **Per-product / per-facet configuration** — scoped data that varies by product axis (see [records-admin-pattern.md](records-admin-pattern.md))
|
|
447
448
|
|
|
448
449
|
### Key Features
|
|
449
450
|
|
|
450
451
|
- **Record types** — `recordType` field for categorization (required)
|
|
451
452
|
- **Time windows** — `startsAt` and `expiresAt` for time-based data
|
|
453
|
+
- **Scoped targeting** — `scope` object restricts which products/variants/facets the record applies to
|
|
454
|
+
- **Specificity scoring** — `specificity` enables "best match" resolution across multiple scoped records
|
|
452
455
|
- **Parent linking** — attach to products, proofs, contacts, etc.
|
|
453
456
|
- **Author tracking** — `authorId` + `authorType`
|
|
454
457
|
- **Status lifecycle** — custom statuses (default `'active'`)
|
|
455
|
-
- **References** — optional `ref` field for external IDs
|
|
458
|
+
- **References** — optional `ref` field for external IDs; auto-derived from scope if omitted
|
|
459
|
+
|
|
460
|
+
### Scoped Records
|
|
461
|
+
|
|
462
|
+
Every record has a `scope` object. An empty scope (`{}`) means the record is universal — it applies everywhere. A populated scope restricts which context the record applies to.
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
import { app } from '@proveanything/smartlinks';
|
|
466
|
+
|
|
467
|
+
// A nutrition record scoped to a specific product
|
|
468
|
+
await app.records.create(collectionId, appId, {
|
|
469
|
+
recordType: 'nutrition',
|
|
470
|
+
scope: { productId: 'prod_abc' },
|
|
471
|
+
data: { calories: 250, protein: 12.5 },
|
|
472
|
+
}, true);
|
|
473
|
+
|
|
474
|
+
// A nutrition record for a specific variant, overriding the product-level record
|
|
475
|
+
await app.records.create(collectionId, appId, {
|
|
476
|
+
recordType: 'nutrition',
|
|
477
|
+
scope: { productId: 'prod_abc', variantId: 'var_500ml' },
|
|
478
|
+
data: { calories: 260, protein: 12.5 },
|
|
479
|
+
}, true);
|
|
480
|
+
|
|
481
|
+
// A record scoped to a facet value (applies to all products with tier=gold)
|
|
482
|
+
await app.records.create(collectionId, appId, {
|
|
483
|
+
recordType: 'loyalty_promo',
|
|
484
|
+
scope: {
|
|
485
|
+
facets: [{ key: 'tier', valueKeys: ['gold', 'platinum'] }]
|
|
486
|
+
},
|
|
487
|
+
data: { discountPercent: 15 },
|
|
488
|
+
}, true);
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
The `ref` field is derived automatically when `scope` is provided and `ref` is omitted:
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
scope: { productId: 'prod_abc' } → ref: 'product:prod_abc'
|
|
495
|
+
scope: { productId: 'prod_abc', variantId: 'var_x' } → ref: 'product:prod_abc/variant:var_x'
|
|
496
|
+
scope: {} → ref: '' (universal)
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### Specificity scores
|
|
500
|
+
|
|
501
|
+
When multiple scoped records match a context, they are ordered by `specificity`. Higher = more specific:
|
|
502
|
+
|
|
503
|
+
| Scope element | Points |
|
|
504
|
+
|---------------|--------|
|
|
505
|
+
| `proofId` | +1000 |
|
|
506
|
+
| `batchId` | +500 |
|
|
507
|
+
| `variantId` | +250 |
|
|
508
|
+
| `productId` | +100 |
|
|
509
|
+
| Per facet clause | +10 |
|
|
510
|
+
| Per facet value key | +1 |
|
|
511
|
+
|
|
512
|
+
### Matching Records Against a Context
|
|
513
|
+
|
|
514
|
+
Use `app.records.match()` to find records whose scope is satisfied by a runtime target:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// Find all nutrition records that apply for this product + facet context
|
|
518
|
+
const { records } = await app.records.match(collectionId, appId, {
|
|
519
|
+
target: {
|
|
520
|
+
productId: 'prod_abc',
|
|
521
|
+
facets: { tier: ['gold'] },
|
|
522
|
+
},
|
|
523
|
+
recordType: 'nutrition',
|
|
524
|
+
}, true);
|
|
525
|
+
// records is ordered by specificity descending — most specific first
|
|
526
|
+
|
|
527
|
+
// Or use strategy: 'best' to get the single winner per recordType
|
|
528
|
+
const { best } = await app.records.match(collectionId, appId, {
|
|
529
|
+
target: { productId: 'prod_abc', variantId: 'var_500ml' },
|
|
530
|
+
strategy: 'best',
|
|
531
|
+
}, true);
|
|
532
|
+
// best.nutrition → the highest-specificity nutrition record for this context
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Facet matching rules:
|
|
536
|
+
- Multiple `facets` clauses are **ANDed** — all must be satisfied
|
|
537
|
+
- Values within a single clause are **ORed** — any matching value satisfies it
|
|
538
|
+
- A record with no facet clauses is satisfied by any target
|
|
539
|
+
|
|
540
|
+
### Upsert
|
|
541
|
+
|
|
542
|
+
Create-or-update a record by `ref` in a single call:
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
const { created } = await app.records.upsert(collectionId, appId, {
|
|
546
|
+
ref: 'product:prod_abc',
|
|
547
|
+
recordType: 'nutrition',
|
|
548
|
+
scope: { productId: 'prod_abc' },
|
|
549
|
+
data: { calories: 250, protein: 12.5 },
|
|
550
|
+
});
|
|
551
|
+
// created: true if new, false if updated
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Bulk Operations
|
|
555
|
+
|
|
556
|
+
Upsert or delete large sets of records efficiently:
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
// Bulk upsert up to 500 records in one transaction
|
|
560
|
+
const result = await app.records.bulkUpsert(collectionId, appId, [
|
|
561
|
+
{ ref: 'product:prod_abc', recordType: 'nutrition', scope: { productId: 'prod_abc' }, data: { calories: 250 } },
|
|
562
|
+
{ ref: 'product:prod_xyz', recordType: 'nutrition', scope: { productId: 'prod_xyz' }, data: { calories: 180 } },
|
|
563
|
+
]);
|
|
564
|
+
// result: { saved: 2, failed: 0, results: [...] }
|
|
565
|
+
|
|
566
|
+
// Bulk delete by explicit refs
|
|
567
|
+
await app.records.bulkDelete(collectionId, appId, {
|
|
568
|
+
refs: ['product:prod_abc', 'product:prod_xyz'],
|
|
569
|
+
recordType: 'nutrition',
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Bulk delete by scope anchor (all records under a product)
|
|
573
|
+
await app.records.bulkDelete(collectionId, appId, {
|
|
574
|
+
scope: { productId: 'prod_abc' },
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Restore a Soft-Deleted Record
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
const restored = await app.records.restore(collectionId, appId, recordId);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Scheduling Filters
|
|
585
|
+
|
|
586
|
+
`startsAt` and `expiresAt` control record active windows. The list and match endpoints respect scheduling by default (only returning currently-active records). Override with:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// Include future records and expired records
|
|
590
|
+
await app.records.list(collectionId, appId, {
|
|
591
|
+
includeScheduled: true,
|
|
592
|
+
includeExpired: true,
|
|
593
|
+
}, true);
|
|
594
|
+
|
|
595
|
+
// Preview what records will be active at a future point in time
|
|
596
|
+
await app.records.match(collectionId, appId, {
|
|
597
|
+
target: { productId: 'prod_abc' },
|
|
598
|
+
at: '2026-06-01T00:00:00Z',
|
|
599
|
+
}, true);
|
|
600
|
+
```
|
|
456
601
|
|
|
457
602
|
### Example: Product Registration
|
|
458
603
|
|