@proveanything/smartlinks 1.9.23 → 1.10.1
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 +17 -8
- package/dist/api/appObjects.js +23 -0
- package/dist/docs/API_SUMMARY.md +113 -6
- package/dist/docs/app-objects.md +191 -2
- package/dist/docs/records-admin-pattern.md +32 -13
- package/dist/docs/ui-utils.md +3 -3
- package/dist/openapi.yaml +968 -923
- package/dist/types/appObjects.d.ts +159 -4
- package/docs/API_SUMMARY.md +113 -6
- package/docs/app-objects.md +191 -2
- package/docs/records-admin-pattern.md +32 -13
- package/docs/ui-utils.md +3 -3
- package/openapi.yaml +968 -923
- package/package.json +1 -1
|
@@ -284,6 +284,32 @@ export interface ThreadListQueryParams extends ListQueryParams {
|
|
|
284
284
|
tag?: string;
|
|
285
285
|
contactId?: string;
|
|
286
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* A single clause in a FacetRule. Tests one facet key against one or more values (OR semantics).
|
|
289
|
+
*/
|
|
290
|
+
export interface FacetRuleClause {
|
|
291
|
+
/**
|
|
292
|
+
* Facet key this clause tests, e.g. "brand", "type", "bread-type".
|
|
293
|
+
* Must reference a defined facet on the collection.
|
|
294
|
+
*/
|
|
295
|
+
facetKey: string;
|
|
296
|
+
/**
|
|
297
|
+
* One or more facet value keys that satisfy the clause (OR semantics).
|
|
298
|
+
* At least one value required. Server deduplicates and sorts.
|
|
299
|
+
*/
|
|
300
|
+
anyOf: string[];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Multi-clause boolean facet rule: AND across clauses, OR within each clause's anyOf.
|
|
304
|
+
* Mutually exclusive with `scope` on a record.
|
|
305
|
+
*/
|
|
306
|
+
export interface FacetRule {
|
|
307
|
+
/**
|
|
308
|
+
* All clauses must be satisfied (AND semantics).
|
|
309
|
+
* Must be non-empty; no duplicate facetKey entries.
|
|
310
|
+
*/
|
|
311
|
+
all: FacetRuleClause[];
|
|
312
|
+
}
|
|
287
313
|
/**
|
|
288
314
|
* Facet clause within a RecordScope.
|
|
289
315
|
* Values within a single clause are ORed; multiple clauses are ANDed.
|
|
@@ -340,6 +366,8 @@ export interface BulkUpsertItem {
|
|
|
340
366
|
scope?: RecordScope;
|
|
341
367
|
data?: Record<string, unknown> | null;
|
|
342
368
|
metadata?: Record<string, unknown> | null;
|
|
369
|
+
/** Facet rule (rule records only). Mutually exclusive with scope. */
|
|
370
|
+
facetRule?: FacetRule | null;
|
|
343
371
|
}
|
|
344
372
|
/**
|
|
345
373
|
* Response from the bulk-upsert endpoint.
|
|
@@ -371,17 +399,66 @@ export interface BulkUpsertResult {
|
|
|
371
399
|
export interface BulkDeleteResult {
|
|
372
400
|
deleted: number;
|
|
373
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Input for the bulk-delete endpoint.
|
|
404
|
+
* Use **refs mode** to delete explicit records by ref,
|
|
405
|
+
* or **scope mode** to delete all records anchored to a scope.
|
|
406
|
+
*/
|
|
407
|
+
export type BulkDeleteInput = {
|
|
408
|
+
refs: string[];
|
|
409
|
+
recordType?: string;
|
|
410
|
+
scope?: never;
|
|
411
|
+
} | {
|
|
412
|
+
scope: Omit<RecordScope, 'facets'>;
|
|
413
|
+
recordType?: string;
|
|
414
|
+
refs?: never;
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Which resolution tier caused a record to be selected in `match()` or `resolveAll()`.
|
|
418
|
+
* Precedence (highest first): proof > batch > variant > product > rule > facet > collection > universal.
|
|
419
|
+
*/
|
|
420
|
+
export type MatchedAt = 'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'facet' | 'collection' | 'universal';
|
|
421
|
+
/**
|
|
422
|
+
* An AppRecord augmented with `matchedAt` — present only on records returned
|
|
423
|
+
* by the `match` endpoint. Use this to display attribution such as
|
|
424
|
+
* "Inherited from product" or "Batch-specific" without inspecting scope fields.
|
|
425
|
+
*/
|
|
426
|
+
export interface MatchedRecord extends AppRecord {
|
|
427
|
+
/**
|
|
428
|
+
* The most specific scope dimension that caused this record to match.
|
|
429
|
+
* 'universal' means the record has an empty scope and matches all contexts.
|
|
430
|
+
*/
|
|
431
|
+
matchedAt: MatchedAt;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* An entry in `match()` or `resolveAll()` results — the record plus resolution metadata.
|
|
435
|
+
*/
|
|
436
|
+
export interface MatchEntry {
|
|
437
|
+
/** The matched record. */
|
|
438
|
+
record: AppRecord;
|
|
439
|
+
/** Which resolution tier caused this record to be selected. */
|
|
440
|
+
matchedAt: MatchedAt;
|
|
441
|
+
/** The rule that fired. Present only when matchedAt === 'rule'. */
|
|
442
|
+
matchedRule?: FacetRule;
|
|
443
|
+
/**
|
|
444
|
+
* Number of clauses in the rule that fired.
|
|
445
|
+
* Present only when matchedAt === 'rule'.
|
|
446
|
+
*/
|
|
447
|
+
matchedClauseCount?: number;
|
|
448
|
+
/** Numeric specificity score. Higher = more specific. */
|
|
449
|
+
specificity: number;
|
|
450
|
+
}
|
|
374
451
|
/**
|
|
375
452
|
* Response from the match endpoint.
|
|
376
453
|
*/
|
|
377
454
|
export interface MatchResult {
|
|
378
455
|
/** Matched records ordered by specificity descending (most specific first) */
|
|
379
|
-
records:
|
|
456
|
+
records: MatchEntry[];
|
|
380
457
|
/**
|
|
381
458
|
* Only present when strategy is 'best'.
|
|
382
459
|
* The single highest-specificity record per recordType.
|
|
383
460
|
*/
|
|
384
|
-
best?: Record<string,
|
|
461
|
+
best?: Record<string, MatchEntry>;
|
|
385
462
|
}
|
|
386
463
|
/**
|
|
387
464
|
* Request body for the upsert endpoint.
|
|
@@ -398,6 +475,8 @@ export interface UpsertRecordInput {
|
|
|
398
475
|
scope?: RecordScope;
|
|
399
476
|
data?: Record<string, unknown> | null;
|
|
400
477
|
metadata?: Record<string, unknown> | null;
|
|
478
|
+
/** Facet rule (rule records only). Mutually exclusive with scope. */
|
|
479
|
+
facetRule?: FacetRule | null;
|
|
401
480
|
}
|
|
402
481
|
/**
|
|
403
482
|
* Response from the upsert endpoint — includes AppRecord plus a created flag.
|
|
@@ -442,7 +521,9 @@ export interface AppRecord {
|
|
|
442
521
|
customId: string | null;
|
|
443
522
|
sourceSystem: string | null;
|
|
444
523
|
status: string | null;
|
|
524
|
+
/** @deprecated use scope.productId instead */
|
|
445
525
|
productId: string | null;
|
|
526
|
+
/** @deprecated use scope.proofId instead */
|
|
446
527
|
proofId: string | null;
|
|
447
528
|
contactId: string | null;
|
|
448
529
|
authorId: string | null;
|
|
@@ -464,6 +545,12 @@ export interface AppRecord {
|
|
|
464
545
|
* Higher = more specific. 0 = universal scope.
|
|
465
546
|
*/
|
|
466
547
|
specificity: number;
|
|
548
|
+
/**
|
|
549
|
+
* Facet rule for rule records (ref starts with "rule:").
|
|
550
|
+
* null on all other record types. Mutually exclusive with scope.
|
|
551
|
+
* SDK 1.10.
|
|
552
|
+
*/
|
|
553
|
+
facetRule: FacetRule | null;
|
|
467
554
|
data: Record<string, unknown>;
|
|
468
555
|
owner: Record<string, unknown>;
|
|
469
556
|
admin: Record<string, unknown>;
|
|
@@ -494,6 +581,8 @@ export interface CreateRecordInput {
|
|
|
494
581
|
owner?: Record<string, unknown>;
|
|
495
582
|
admin?: Record<string, unknown>;
|
|
496
583
|
metadata?: Record<string, unknown>;
|
|
584
|
+
/** Facet rule (rule records only). Mutually exclusive with scope. */
|
|
585
|
+
facetRule?: FacetRule | null;
|
|
497
586
|
}
|
|
498
587
|
/**
|
|
499
588
|
* Input for updating a record
|
|
@@ -513,6 +602,8 @@ export interface UpdateRecordInput {
|
|
|
513
602
|
customId?: string;
|
|
514
603
|
sourceSystem?: string;
|
|
515
604
|
metadata?: Record<string, unknown>;
|
|
605
|
+
/** Facet rule (rule records only). Mutually exclusive with scope. Send null to clear. */
|
|
606
|
+
facetRule?: FacetRule | null;
|
|
516
607
|
}
|
|
517
608
|
/**
|
|
518
609
|
* Query parameters for listing records
|
|
@@ -524,9 +615,7 @@ export interface RecordListQueryParams extends ListQueryParams {
|
|
|
524
615
|
refPrefix?: string;
|
|
525
616
|
customId?: string;
|
|
526
617
|
sourceSystem?: string;
|
|
527
|
-
status?: string;
|
|
528
618
|
proofId?: string;
|
|
529
|
-
productId?: string;
|
|
530
619
|
/** Filter by scope.variantId (JSONB lookup) */
|
|
531
620
|
variantId?: string;
|
|
532
621
|
/** Filter by scope.batchId (JSONB lookup) */
|
|
@@ -548,6 +637,72 @@ export interface RecordListQueryParams extends ListQueryParams {
|
|
|
548
637
|
*/
|
|
549
638
|
at?: string;
|
|
550
639
|
contactId?: string;
|
|
640
|
+
/** Include soft-deleted records (non-null deletedAt). Admin only. Default false. */
|
|
641
|
+
includeDeleted?: boolean;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Request body for the resolve-all endpoint.
|
|
645
|
+
* Returns every applicable record for a product context across all tiers.
|
|
646
|
+
*/
|
|
647
|
+
export interface ResolveAllParams {
|
|
648
|
+
/** Product context to evaluate records against. */
|
|
649
|
+
context: {
|
|
650
|
+
productId?: string;
|
|
651
|
+
variantId?: string;
|
|
652
|
+
batchId?: string;
|
|
653
|
+
proofId?: string;
|
|
654
|
+
/**
|
|
655
|
+
* Facet assignments for the product — used for both legacy facet-ref matching
|
|
656
|
+
* and facetRule evaluation.
|
|
657
|
+
* e.g. { "brand": "samsung", "type": ["tv", "laptop"] }
|
|
658
|
+
*/
|
|
659
|
+
facets?: Record<string, string | string[]>;
|
|
660
|
+
};
|
|
661
|
+
/** Limit to a specific record type. Omit to return all types. */
|
|
662
|
+
recordType?: string;
|
|
663
|
+
/** Only return records belonging to these tiers. */
|
|
664
|
+
tiers?: Array<'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'facet' | 'collection'>;
|
|
665
|
+
/** Safety cap. Default 500, max 5000. */
|
|
666
|
+
limit?: number;
|
|
667
|
+
/** Point-in-time for scheduling evaluation (ISO 8601). Defaults to now. */
|
|
668
|
+
at?: string;
|
|
669
|
+
/** Include records whose startsAt is in the future. Default false. */
|
|
670
|
+
includeScheduled?: boolean;
|
|
671
|
+
/** Include records whose expiresAt is in the past. Default false. */
|
|
672
|
+
includeExpired?: boolean;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Response from the resolve-all endpoint.
|
|
676
|
+
*/
|
|
677
|
+
export interface ResolveAllResult {
|
|
678
|
+
/**
|
|
679
|
+
* Every applicable record for the given product context, sorted by precedence
|
|
680
|
+
* (most-specific first). Each record appears at most once.
|
|
681
|
+
*/
|
|
682
|
+
records: MatchEntry[];
|
|
683
|
+
/**
|
|
684
|
+
* true if the result was truncated at the safety cap.
|
|
685
|
+
* Default cap: 500 records. Use `limit` to raise it (max 5000).
|
|
686
|
+
*/
|
|
687
|
+
truncated?: boolean;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Request body for the preview-rule endpoint.
|
|
691
|
+
*/
|
|
692
|
+
export interface PreviewRuleParams {
|
|
693
|
+
/** The facet rule to evaluate (same validation as on record create). */
|
|
694
|
+
facetRule: FacetRule;
|
|
695
|
+
/** Max product IDs to return. Default 20, max 200. */
|
|
696
|
+
limit?: number;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Response from the preview-rule endpoint.
|
|
700
|
+
*/
|
|
701
|
+
export interface PreviewRuleResult {
|
|
702
|
+
/** A sample of product IDs whose facet assignments satisfy the rule. */
|
|
703
|
+
sampleProductIds: string[];
|
|
704
|
+
/** Total products in the collection matching the rule (may exceed sampleProductIds.length). */
|
|
705
|
+
totalMatches: number;
|
|
551
706
|
}
|
|
552
707
|
/**
|
|
553
708
|
* Response from case related endpoint
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.1 | Generated: 2026-04-25T15:35:27.893Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -1880,6 +1880,27 @@ interface ReplyInput {
|
|
|
1880
1880
|
}
|
|
1881
1881
|
```
|
|
1882
1882
|
|
|
1883
|
+
**FacetRuleClause** (interface)
|
|
1884
|
+
```typescript
|
|
1885
|
+
interface FacetRuleClause {
|
|
1886
|
+
* Facet key this clause tests, e.g. "brand", "type", "bread-type".
|
|
1887
|
+
* Must reference a defined facet on the collection.
|
|
1888
|
+
facetKey: string
|
|
1889
|
+
* One or more facet value keys that satisfy the clause (OR semantics).
|
|
1890
|
+
* At least one value required. Server deduplicates and sorts.
|
|
1891
|
+
anyOf: string[]
|
|
1892
|
+
}
|
|
1893
|
+
```
|
|
1894
|
+
|
|
1895
|
+
**FacetRule** (interface)
|
|
1896
|
+
```typescript
|
|
1897
|
+
interface FacetRule {
|
|
1898
|
+
* All clauses must be satisfied (AND semantics).
|
|
1899
|
+
* Must be non-empty; no duplicate facetKey entries.
|
|
1900
|
+
all: FacetRuleClause[]
|
|
1901
|
+
}
|
|
1902
|
+
```
|
|
1903
|
+
|
|
1883
1904
|
**ScopeFacetClause** (interface)
|
|
1884
1905
|
```typescript
|
|
1885
1906
|
interface ScopeFacetClause {
|
|
@@ -1927,6 +1948,7 @@ interface BulkUpsertItem {
|
|
|
1927
1948
|
scope?: RecordScope
|
|
1928
1949
|
data?: Record<string, unknown> | null
|
|
1929
1950
|
metadata?: Record<string, unknown> | null
|
|
1951
|
+
facetRule?: FacetRule | null
|
|
1930
1952
|
}
|
|
1931
1953
|
```
|
|
1932
1954
|
|
|
@@ -1950,13 +1972,26 @@ interface BulkDeleteResult {
|
|
|
1950
1972
|
}
|
|
1951
1973
|
```
|
|
1952
1974
|
|
|
1975
|
+
**MatchEntry** (interface)
|
|
1976
|
+
```typescript
|
|
1977
|
+
interface MatchEntry {
|
|
1978
|
+
record: AppRecord
|
|
1979
|
+
matchedAt: MatchedAt
|
|
1980
|
+
matchedRule?: FacetRule
|
|
1981
|
+
* Number of clauses in the rule that fired.
|
|
1982
|
+
* Present only when matchedAt === 'rule'.
|
|
1983
|
+
matchedClauseCount?: number
|
|
1984
|
+
specificity: number
|
|
1985
|
+
}
|
|
1986
|
+
```
|
|
1987
|
+
|
|
1953
1988
|
**MatchResult** (interface)
|
|
1954
1989
|
```typescript
|
|
1955
1990
|
interface MatchResult {
|
|
1956
|
-
records:
|
|
1991
|
+
records: MatchEntry[]
|
|
1957
1992
|
* Only present when strategy is 'best'.
|
|
1958
1993
|
* The single highest-specificity record per recordType.
|
|
1959
|
-
best?: Record<string,
|
|
1994
|
+
best?: Record<string, MatchEntry>
|
|
1960
1995
|
}
|
|
1961
1996
|
```
|
|
1962
1997
|
|
|
@@ -1973,6 +2008,7 @@ interface UpsertRecordInput {
|
|
|
1973
2008
|
scope?: RecordScope
|
|
1974
2009
|
data?: Record<string, unknown> | null
|
|
1975
2010
|
metadata?: Record<string, unknown> | null
|
|
2011
|
+
facetRule?: FacetRule | null
|
|
1976
2012
|
}
|
|
1977
2013
|
```
|
|
1978
2014
|
|
|
@@ -2004,8 +2040,8 @@ interface AppRecord {
|
|
|
2004
2040
|
customId: string | null
|
|
2005
2041
|
sourceSystem: string | null
|
|
2006
2042
|
status: string | null
|
|
2007
|
-
productId: string | null
|
|
2008
|
-
proofId: string | null
|
|
2043
|
+
productId: string | null
|
|
2044
|
+
proofId: string | null
|
|
2009
2045
|
contactId: string | null
|
|
2010
2046
|
authorId: string | null
|
|
2011
2047
|
authorType: string
|
|
@@ -2022,6 +2058,10 @@ interface AppRecord {
|
|
|
2022
2058
|
* Numeric specificity score computed from scope.
|
|
2023
2059
|
* Higher = more specific. 0 = universal scope.
|
|
2024
2060
|
specificity: number
|
|
2061
|
+
* Facet rule for rule records (ref starts with "rule:").
|
|
2062
|
+
* null on all other record types. Mutually exclusive with scope.
|
|
2063
|
+
* SDK 1.10.
|
|
2064
|
+
facetRule: FacetRule | null
|
|
2025
2065
|
data: Record<string, unknown>
|
|
2026
2066
|
owner: Record<string, unknown>
|
|
2027
2067
|
admin: Record<string, unknown> // admin only
|
|
@@ -2052,6 +2092,7 @@ interface CreateRecordInput {
|
|
|
2052
2092
|
owner?: Record<string, unknown>
|
|
2053
2093
|
admin?: Record<string, unknown> // admin only
|
|
2054
2094
|
metadata?: Record<string, unknown>
|
|
2095
|
+
facetRule?: FacetRule | null
|
|
2055
2096
|
}
|
|
2056
2097
|
```
|
|
2057
2098
|
|
|
@@ -2071,6 +2112,57 @@ interface UpdateRecordInput {
|
|
|
2071
2112
|
customId?: string
|
|
2072
2113
|
sourceSystem?: string
|
|
2073
2114
|
metadata?: Record<string, unknown>
|
|
2115
|
+
facetRule?: FacetRule | null
|
|
2116
|
+
}
|
|
2117
|
+
```
|
|
2118
|
+
|
|
2119
|
+
**ResolveAllParams** (interface)
|
|
2120
|
+
```typescript
|
|
2121
|
+
interface ResolveAllParams {
|
|
2122
|
+
context: {
|
|
2123
|
+
productId?: string
|
|
2124
|
+
variantId?: string
|
|
2125
|
+
batchId?: string
|
|
2126
|
+
proofId?: string
|
|
2127
|
+
* Facet assignments for the product — used for both legacy facet-ref matching
|
|
2128
|
+
* and facetRule evaluation.
|
|
2129
|
+
* e.g. { "brand": "samsung", "type": ["tv", "laptop"] }
|
|
2130
|
+
facets?: Record<string, string | string[]>
|
|
2131
|
+
}
|
|
2132
|
+
recordType?: string
|
|
2133
|
+
tiers?: Array<'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'facet' | 'collection'>
|
|
2134
|
+
limit?: number
|
|
2135
|
+
at?: string
|
|
2136
|
+
includeScheduled?: boolean
|
|
2137
|
+
includeExpired?: boolean
|
|
2138
|
+
}
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
**ResolveAllResult** (interface)
|
|
2142
|
+
```typescript
|
|
2143
|
+
interface ResolveAllResult {
|
|
2144
|
+
* Every applicable record for the given product context, sorted by precedence
|
|
2145
|
+
* (most-specific first). Each record appears at most once.
|
|
2146
|
+
records: MatchEntry[]
|
|
2147
|
+
* true if the result was truncated at the safety cap.
|
|
2148
|
+
* Default cap: 500 records. Use `limit` to raise it (max 5000).
|
|
2149
|
+
truncated?: boolean
|
|
2150
|
+
}
|
|
2151
|
+
```
|
|
2152
|
+
|
|
2153
|
+
**PreviewRuleParams** (interface)
|
|
2154
|
+
```typescript
|
|
2155
|
+
interface PreviewRuleParams {
|
|
2156
|
+
facetRule: FacetRule
|
|
2157
|
+
limit?: number
|
|
2158
|
+
}
|
|
2159
|
+
```
|
|
2160
|
+
|
|
2161
|
+
**PreviewRuleResult** (interface)
|
|
2162
|
+
```typescript
|
|
2163
|
+
interface PreviewRuleResult {
|
|
2164
|
+
sampleProductIds: string[]
|
|
2165
|
+
totalMatches: number
|
|
2074
2166
|
}
|
|
2075
2167
|
```
|
|
2076
2168
|
|
|
@@ -2138,6 +2230,10 @@ interface PublicCreateBranch {
|
|
|
2138
2230
|
|
|
2139
2231
|
**CallerRole** = `'admin' | 'owner' | 'public'`
|
|
2140
2232
|
|
|
2233
|
+
**BulkDeleteInput** = ``
|
|
2234
|
+
|
|
2235
|
+
**MatchedAt** = ``
|
|
2236
|
+
|
|
2141
2237
|
### asset
|
|
2142
2238
|
|
|
2143
2239
|
**Asset** (interface)
|
|
@@ -7299,9 +7395,20 @@ Upsert up to 500 records in a single transaction. Each row is individually error
|
|
|
7299
7395
|
|
|
7300
7396
|
**bulkDelete**(collectionId: string,
|
|
7301
7397
|
appId: string,
|
|
7302
|
-
input:
|
|
7398
|
+
input: BulkDeleteInput) → `Promise<BulkDeleteResult>`
|
|
7303
7399
|
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
7400
|
|
|
7401
|
+
**resolveAll**(collectionId: string,
|
|
7402
|
+
appId: string,
|
|
7403
|
+
input: ResolveAllParams,
|
|
7404
|
+
admin: boolean = false) → `Promise<ResolveAllResult>`
|
|
7405
|
+
Resolve every applicable record for a product context in one call. Returns records across all tiers (proof, batch, variant, product, rule, facet, collection) deduplicated and sorted by specificity descending. POST /records/resolve-all
|
|
7406
|
+
|
|
7407
|
+
**previewRule**(collectionId: string,
|
|
7408
|
+
appId: string,
|
|
7409
|
+
input: PreviewRuleParams) → `Promise<PreviewRuleResult>`
|
|
7410
|
+
Preview which products in the collection match a given facetRule. Admin only. Use for live "matches N products" feedback while authoring a rule. POST /records/preview-rule
|
|
7411
|
+
|
|
7305
7412
|
### app.threads
|
|
7306
7413
|
|
|
7307
7414
|
Conversation-oriented app objects for comments, discussions, Q&A, and reply-driven experiences.
|
package/docs/app-objects.md
CHANGED
|
@@ -523,6 +523,7 @@ const { records } = await app.records.match(collectionId, appId, {
|
|
|
523
523
|
recordType: 'nutrition',
|
|
524
524
|
}, true);
|
|
525
525
|
// records is ordered by specificity descending — most specific first
|
|
526
|
+
// each record carries a `matchedAt` field indicating which scope dimension matched
|
|
526
527
|
|
|
527
528
|
// Or use strategy: 'best' to get the single winner per recordType
|
|
528
529
|
const { best } = await app.records.match(collectionId, appId, {
|
|
@@ -537,6 +538,101 @@ Facet matching rules:
|
|
|
537
538
|
- Values within a single clause are **ORed** — any matching value satisfies it
|
|
538
539
|
- A record with no facet clauses is satisfied by any target
|
|
539
540
|
|
|
541
|
+
#### `matchedAt` — match attribution
|
|
542
|
+
|
|
543
|
+
Every record in the response includes a `matchedAt` field indicating **which scope dimension caused the match**. Use it to render attribution labels without inspecting scope fields:
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
const { records } = await app.records.match(collectionId, appId, { target, recordType: 'nutrition' }, true);
|
|
547
|
+
|
|
548
|
+
for (const entry of records) {
|
|
549
|
+
switch (entry.matchedAt) {
|
|
550
|
+
case 'proof': /* "Scan-specific" */ break;
|
|
551
|
+
case 'batch': /* "Batch-specific" */ break;
|
|
552
|
+
case 'variant': /* "Size-specific" */ break;
|
|
553
|
+
case 'product': /* "Inherited from product" */ break;
|
|
554
|
+
case 'rule': /* "Matches rule" */ break;
|
|
555
|
+
case 'facet': /* "Tier-specific" */ break;
|
|
556
|
+
case 'collection': /* "Collection default" */ break;
|
|
557
|
+
case 'universal': /* "Default" */ break;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Precedence follows: `proof > batch > variant > product > rule > facet > collection > universal`.
|
|
563
|
+
|
|
564
|
+
#### React — `useResolvedRecord`
|
|
565
|
+
|
|
566
|
+
For React consumers, the `useResolvedRecord` hook in `@proveanything/smartlinks-utils-ui` wraps `records.match()` and returns the best-matching record with loading and error states. The raw `records.match()` API exists for non-React consumers and custom resolution logic.
|
|
567
|
+
|
|
568
|
+
### Facet-Rule Records
|
|
569
|
+
|
|
570
|
+
A record can declare a **multi-clause boolean rule** (`facetRule`) describing which products it applies to, instead of a single `scope.facets` entry. The rule is AND across facet keys, OR within values of each key:
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
// Create a record that matches all Samsung TVs and laptops
|
|
574
|
+
await app.records.create(collectionId, appId, {
|
|
575
|
+
recordType: 'warranty',
|
|
576
|
+
facetRule: {
|
|
577
|
+
all: [
|
|
578
|
+
{ facetKey: 'brand', anyOf: ['samsung'] },
|
|
579
|
+
{ facetKey: 'type', anyOf: ['tv', 'laptop'] },
|
|
580
|
+
],
|
|
581
|
+
},
|
|
582
|
+
data: { warrantyYears: 2 },
|
|
583
|
+
}, true);
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`facetRule` is **mutually exclusive with `scope`**. A record has either a structured scope or a facetRule, never both. The server assigns `ref: 'rule:<ulid>'` automatically.
|
|
587
|
+
|
|
588
|
+
Specificity for rule records: `Σ (50 + clause.anyOf.length)` across all clauses. A 2-clause rule with 1 value each scores `(50+1)+(50+1) = 102`, which ranks above a plain product-scoped record (100) in `resolveAll()` results.
|
|
589
|
+
|
|
590
|
+
Use `records.previewRule()` to see which products a rule would match before creating it:
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
const { sampleProductIds, totalMatches } = await app.records.previewRule(collectionId, appId, {
|
|
594
|
+
facetRule: {
|
|
595
|
+
all: [{ facetKey: 'brand', anyOf: ['samsung'] }],
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
// totalMatches: 42, sampleProductIds: ['prod_001', 'prod_002', ...]
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Resolve All
|
|
602
|
+
|
|
603
|
+
Use `app.records.resolveAll()` to fetch **every applicable record for a product context** in one request—across all tiers (proof, batch, variant, product, rule, facet, collection defaults), deduplicated and sorted by specificity:
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
// All records that apply to this product context (admin)
|
|
607
|
+
const { records, truncated } = await app.records.resolveAll(collectionId, appId, {
|
|
608
|
+
context: {
|
|
609
|
+
productId: 'prod_001',
|
|
610
|
+
facets: { brand: 'samsung', type: 'tv' },
|
|
611
|
+
},
|
|
612
|
+
recordType: 'warranty',
|
|
613
|
+
}, true);
|
|
614
|
+
|
|
615
|
+
for (const entry of records) {
|
|
616
|
+
console.log(entry.matchedAt, entry.specificity, entry.record.id);
|
|
617
|
+
if (entry.matchedAt === 'rule') {
|
|
618
|
+
console.log('rule fired:', entry.matchedRule, 'clauses:', entry.matchedClauseCount);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Public endpoint — visibility-filtered (admin records excluded)
|
|
623
|
+
const { records: publicRecords } = await app.records.resolveAll(collectionId, appId, {
|
|
624
|
+
context: { productId: 'prod_001', facets: { brand: 'samsung' } },
|
|
625
|
+
}, false);
|
|
626
|
+
|
|
627
|
+
// Filter to specific tiers
|
|
628
|
+
const { records: ruleRecords } = await app.records.resolveAll(collectionId, appId, {
|
|
629
|
+
context: { productId: 'prod_001', facets: { brand: 'samsung', type: 'tv' } },
|
|
630
|
+
tiers: ['product', 'rule', 'collection'],
|
|
631
|
+
}, true);
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
`truncated: true` means the result hit the safety cap (default 500). Raise it with `limit` (max 5000).
|
|
635
|
+
|
|
540
636
|
### Upsert
|
|
541
637
|
|
|
542
638
|
Create-or-update a record by `ref` in a single call:
|
|
@@ -575,13 +671,106 @@ await app.records.bulkDelete(collectionId, appId, {
|
|
|
575
671
|
});
|
|
576
672
|
```
|
|
577
673
|
|
|
578
|
-
###
|
|
674
|
+
### Soft-Delete Semantics
|
|
675
|
+
|
|
676
|
+
`delete` and `bulkDelete` **soft-delete** records: the row is retained with a non-null `deletedAt` and excluded from all queries by default. Records are **recoverable indefinitely** — there is no expiry on `deletedAt`.
|
|
579
677
|
|
|
580
678
|
```typescript
|
|
679
|
+
// Single-record restore
|
|
581
680
|
const restored = await app.records.restore(collectionId, appId, recordId);
|
|
681
|
+
|
|
682
|
+
// List including deleted records (admin only)
|
|
683
|
+
const all = await app.records.list(collectionId, appId, {
|
|
684
|
+
recordType: 'nutrition',
|
|
685
|
+
includeDeleted: true,
|
|
686
|
+
}, true);
|
|
687
|
+
// all.data includes records with non-null deletedAt
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
`bulkDelete` is fully reversible: rows survive with their IDs intact. Restore individually via `restore`, or re-write via `bulkUpsert` (which will find the existing row by `ref` and update it, clearing `deletedAt` in the process).
|
|
691
|
+
|
|
692
|
+
### Text Search
|
|
693
|
+
|
|
694
|
+
The `q` parameter on `GET /records` performs a **case-insensitive substring match** (`ILIKE`) on `data->>'label'`. It works on both admin and public list endpoints today:
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
const results = await app.records.list(collectionId, appId, {
|
|
698
|
+
recordType: 'product',
|
|
699
|
+
q: 'premium',
|
|
700
|
+
}, true);
|
|
701
|
+
// returns records where data.label contains 'premium' (case-insensitive)
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
> `q` is not a full-text index and does not return ranked results. For ranked relevance search over large corpora, use the Elasticsearch integration.
|
|
705
|
+
|
|
706
|
+
### External ID / ETL Workflow
|
|
707
|
+
|
|
708
|
+
`customId` and `sourceSystem` provide a stable external key pair for loading records from external systems (CMS, ERP, PIM, etc.):
|
|
709
|
+
|
|
710
|
+
- Both fields are **indexed** via a composite index on `(sourceSystem, customIdNormalized)`.
|
|
711
|
+
- `customId` is **filterable** on `GET /records?customId=x&sourceSystem=y`.
|
|
712
|
+
- The pair is **not unique** — the same external ID can exist across different `recordType` values by design (a CMS slug can appear in both a `content` and a `nutrition` record).
|
|
713
|
+
- `upsert` currently keys on `ref`, not `customId`. The recommended ETL pattern is to derive a stable `ref` from the external ID and pass `customId` alongside:
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
// Idiomatic ETL upsert: ref is derived from the external key, customId carries it too
|
|
717
|
+
await app.records.upsert(collectionId, appId, {
|
|
718
|
+
ref: `cms:${slug}`, // stable find-or-create key
|
|
719
|
+
customId: slug,
|
|
720
|
+
sourceSystem: 'contentful',
|
|
721
|
+
recordType: 'content_page',
|
|
722
|
+
scope: { productId },
|
|
723
|
+
data: { title, body },
|
|
724
|
+
});
|
|
725
|
+
// upsert finds-or-creates by ref deterministically,
|
|
726
|
+
// customId is stored for later reverse-lookup via ?customId=&sourceSystem=
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### Counts by Record Type
|
|
730
|
+
|
|
731
|
+
`aggregate()` returns counts grouped by `record_type` in a single round-trip — no separate endpoint needed:
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
const stats = await app.records.aggregate(collectionId, appId, {
|
|
735
|
+
groupBy: ['record_type'],
|
|
736
|
+
metrics: ['count'],
|
|
737
|
+
// Optionally narrow the corpus:
|
|
738
|
+
filters: { status: 'active' },
|
|
739
|
+
}, true);
|
|
740
|
+
// stats.groups → [{ record_type: 'nutrition', count: 42 }, { record_type: 'loyalty_promo', count: 7 }, ...]
|
|
741
|
+
// Ordered by count descending
|
|
582
742
|
```
|
|
583
743
|
|
|
584
|
-
|
|
744
|
+
You can also combine with other filters:
|
|
745
|
+
|
|
746
|
+
```typescript
|
|
747
|
+
// Counts per type for a specific product
|
|
748
|
+
await app.records.aggregate(collectionId, appId, {
|
|
749
|
+
groupBy: ['record_type'],
|
|
750
|
+
metrics: ['count'],
|
|
751
|
+
filters: { product_id: 'prod_abc' },
|
|
752
|
+
}, true);
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Canonical Ref Format
|
|
756
|
+
|
|
757
|
+
The `ref` field is **server-derived** when you provide a `scope` and omit `ref`. Clients should never construct ref strings manually. The authoritative grammar is slash-joined:
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
[facet:{key}={val1}+{val2}/][product:{productId}/][variant:{variantId}/][batch:{batchId}/][proof:{proofId}]
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Examples:
|
|
764
|
+
|
|
765
|
+
| Scope | Derived ref |
|
|
766
|
+
|-------|-------------|
|
|
767
|
+
| `{ productId: 'prod_abc' }` | `product:prod_abc` |
|
|
768
|
+
| `{ productId: 'prod_abc', variantId: 'var_500ml' }` | `product:prod_abc/variant:var_500ml` |
|
|
769
|
+
| `{ batchId: 'batch_q1' }` | `batch:batch_q1` |
|
|
770
|
+
| `{ facets: [{ key: 'tier', valueKeys: ['gold'] }] }` | `facet:tier=gold` |
|
|
771
|
+
| `{}` | `''` (universal) |
|
|
772
|
+
|
|
773
|
+
`parseRef` / `buildRef` in `data/refs.ts` should be used for **display and URL round-tripping only**, never as upsert keys. For ETL use cases, set an explicit `ref` using a stable external key (see [External ID / ETL Workflow](#external-id--etl-workflow)).
|
|
585
774
|
|
|
586
775
|
`startsAt` and `expiresAt` control record active windows. The list and match endpoints respect scheduling by default (only returning currently-active records). Override with:
|
|
587
776
|
|