@proveanything/smartlinks 1.9.21 → 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.
@@ -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
- status: string;
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
  /**
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.9.21 | Generated: 2026-04-25T10:48:10.932Z
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
- status: string // default 'active'
1894
- productId: string | null
1895
- proofId: string | null
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.
@@ -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
 
package/docs/overview.md CHANGED
@@ -67,7 +67,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
67
67
  | **Forms** | `docs/forms.md` | Form definitions, schema-driven rendering, submission patterns |
68
68
  | **Auth Kit** | `docs/auth-kit.md` | End-user sign-in: email/password, magic links, phone OTP, Google OAuth |
69
69
  | **Records Admin Pattern** | `docs/records-admin-pattern.md` | Standard pattern for per-product/facet/variant/batch admin UIs |
70
- | **UI Utils** | `docs/ui-utils.md` | `@proveanything/ui-utils` — React shells, hooks, and primitives for records-based apps |
70
+ | **UI Utils** | `docs/ui-utils.md` | `@proveanything/smartlinks-utils-ui` — React shells, hooks, and primitives for records-based apps |
71
71
 
72
72
  ---
73
73