@proveanything/smartlinks 1.10.0 → 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.
@@ -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.
@@ -386,10 +414,10 @@ export type BulkDeleteInput = {
386
414
  refs?: never;
387
415
  };
388
416
  /**
389
- * Indicates which scope dimension caused a record to match during `match()`.
390
- * Follows specificity order: proof > batch > variant > product > facet > universal.
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.
391
419
  */
392
- export type MatchedAtLevel = 'proof' | 'batch' | 'variant' | 'product' | 'facet' | 'universal';
420
+ export type MatchedAt = 'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'facet' | 'collection' | 'universal';
393
421
  /**
394
422
  * An AppRecord augmented with `matchedAt` — present only on records returned
395
423
  * by the `match` endpoint. Use this to display attribution such as
@@ -400,19 +428,37 @@ export interface MatchedRecord extends AppRecord {
400
428
  * The most specific scope dimension that caused this record to match.
401
429
  * 'universal' means the record has an empty scope and matches all contexts.
402
430
  */
403
- matchedAt: MatchedAtLevel;
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;
404
450
  }
405
451
  /**
406
452
  * Response from the match endpoint.
407
453
  */
408
454
  export interface MatchResult {
409
455
  /** Matched records ordered by specificity descending (most specific first) */
410
- records: MatchedRecord[];
456
+ records: MatchEntry[];
411
457
  /**
412
458
  * Only present when strategy is 'best'.
413
459
  * The single highest-specificity record per recordType.
414
460
  */
415
- best?: Record<string, MatchedRecord>;
461
+ best?: Record<string, MatchEntry>;
416
462
  }
417
463
  /**
418
464
  * Request body for the upsert endpoint.
@@ -429,6 +475,8 @@ export interface UpsertRecordInput {
429
475
  scope?: RecordScope;
430
476
  data?: Record<string, unknown> | null;
431
477
  metadata?: Record<string, unknown> | null;
478
+ /** Facet rule (rule records only). Mutually exclusive with scope. */
479
+ facetRule?: FacetRule | null;
432
480
  }
433
481
  /**
434
482
  * Response from the upsert endpoint — includes AppRecord plus a created flag.
@@ -497,6 +545,12 @@ export interface AppRecord {
497
545
  * Higher = more specific. 0 = universal scope.
498
546
  */
499
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;
500
554
  data: Record<string, unknown>;
501
555
  owner: Record<string, unknown>;
502
556
  admin: Record<string, unknown>;
@@ -527,6 +581,8 @@ export interface CreateRecordInput {
527
581
  owner?: Record<string, unknown>;
528
582
  admin?: Record<string, unknown>;
529
583
  metadata?: Record<string, unknown>;
584
+ /** Facet rule (rule records only). Mutually exclusive with scope. */
585
+ facetRule?: FacetRule | null;
530
586
  }
531
587
  /**
532
588
  * Input for updating a record
@@ -546,6 +602,8 @@ export interface UpdateRecordInput {
546
602
  customId?: string;
547
603
  sourceSystem?: string;
548
604
  metadata?: Record<string, unknown>;
605
+ /** Facet rule (rule records only). Mutually exclusive with scope. Send null to clear. */
606
+ facetRule?: FacetRule | null;
549
607
  }
550
608
  /**
551
609
  * Query parameters for listing records
@@ -582,6 +640,70 @@ export interface RecordListQueryParams extends ListQueryParams {
582
640
  /** Include soft-deleted records (non-null deletedAt). Admin only. Default false. */
583
641
  includeDeleted?: boolean;
584
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;
706
+ }
585
707
  /**
586
708
  * Response from case related endpoint
587
709
  */
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.10.0 | Generated: 2026-04-25T13:26:09.113Z
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: MatchedRecord[]
1991
+ records: MatchEntry[]
1957
1992
  * Only present when strategy is 'best'.
1958
1993
  * The single highest-specificity record per recordType.
1959
- best?: Record<string, MatchedRecord>
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
 
@@ -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
 
@@ -2140,7 +2232,7 @@ interface PublicCreateBranch {
2140
2232
 
2141
2233
  **BulkDeleteInput** = ``
2142
2234
 
2143
- **MatchedAtLevel** = ``
2235
+ **MatchedAt** = ``
2144
2236
 
2145
2237
  ### asset
2146
2238
 
@@ -7306,6 +7398,17 @@ Upsert up to 500 records in a single transaction. Each row is individually error
7306
7398
  input: BulkDeleteInput) → `Promise<BulkDeleteResult>`
7307
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' }, }); ```
7308
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
+
7309
7412
  ### app.threads
7310
7413
 
7311
7414
  Conversation-oriented app objects for comments, discussions, Q&A, and reply-driven experiences.
@@ -545,24 +545,94 @@ Every record in the response includes a `matchedAt` field indicating **which sco
545
545
  ```typescript
546
546
  const { records } = await app.records.match(collectionId, appId, { target, recordType: 'nutrition' }, true);
547
547
 
548
- for (const record of records) {
549
- switch (record.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 'facet': /* "Tier-specific" */ break;
555
- case 'universal': /* "Default" */ break;
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;
556
558
  }
557
559
  }
558
560
  ```
559
561
 
560
- Precedence follows specificity order: `proof > batch > variant > product > facet > universal`.
562
+ Precedence follows: `proof > batch > variant > product > rule > facet > collection > universal`.
561
563
 
562
564
  #### React — `useResolvedRecord`
563
565
 
564
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.
565
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
+
566
636
  ### Upsert
567
637
 
568
638
  Create-or-update a record by `ref` in a single call: