@proveanything/smartlinks 1.15.24 → 1.16.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.
@@ -34,6 +34,7 @@ export { order } from "./order";
34
34
  export { app } from "./appObjects";
35
35
  export { attestations } from "./attestations";
36
36
  export { containers } from "./containers";
37
+ export { lots } from "./lots";
37
38
  export { loyalty } from "./loyalty";
38
39
  export { translations } from "./translations";
39
40
  export { config } from "./config";
package/dist/api/index.js CHANGED
@@ -37,6 +37,7 @@ export { order } from "./order";
37
37
  export { app } from "./appObjects";
38
38
  export { attestations } from "./attestations";
39
39
  export { containers } from "./containers";
40
+ export { lots } from "./lots";
40
41
  export { loyalty } from "./loyalty";
41
42
  export { translations } from "./translations";
42
43
  export { config } from "./config";
@@ -0,0 +1,50 @@
1
+ import { Lot, LotCreateInput, LotUpdateInput, ListLotsParams, ResolveLotResponse, ListLotProductsResponse } from "../types/lots";
2
+ /**
3
+ * Lots — collection-scoped production groupings that span one or more products.
4
+ * Writes and admin reads hit `/admin/collection/:cid/lots`; the `public*` reads hit
5
+ * `/public/collection/:cid/lots` for cross-app consumers. Admin vs public is the path
6
+ * prefix (auth is the ambient bearer token); there is no `admin` flag in this SDK.
7
+ */
8
+ export declare namespace lots {
9
+ /** Create a lot (resolves its selector into members). */
10
+ function create(collectionId: string, lot: LotCreateInput): Promise<Lot>;
11
+ /** List lots (summary rows; `payload`/`productIds` omitted). Filter by status, search, or containing productId. */
12
+ function list(collectionId: string, params?: ListLotsParams): Promise<Lot[]>;
13
+ /** Get the full lot record. Pass `{ includeDeleted: true }` to fetch a soft-deleted one. */
14
+ function get(collectionId: string, lotId: string, opts?: {
15
+ includeDeleted?: boolean;
16
+ }): Promise<Lot>;
17
+ /** Look up a lot by its number (case-insensitive) — used by scan/resolver flows. */
18
+ function getByNumber(collectionId: string, lotNumber: string, opts?: {
19
+ includeDeleted?: boolean;
20
+ }): Promise<Lot>;
21
+ /** Update a lot. Re-resolves members if the selector changed (response then carries `diff`). */
22
+ function update(collectionId: string, lotId: string, lot: LotUpdateInput): Promise<Lot>;
23
+ /**
24
+ * Soft-delete a lot — recoverable, and frees its `lotNumber` for reuse. Distinct from
25
+ * {@link archive}. Hidden from reads unless `{ includeDeleted: true }`; undo with {@link restore}.
26
+ */
27
+ function remove(collectionId: string, lotId: string): Promise<{
28
+ success: boolean;
29
+ }>;
30
+ /** Archive a lot — a live lifecycle state (stays visible, keeps its number). Not a delete. */
31
+ function archive(collectionId: string, lotId: string): Promise<{
32
+ success: boolean;
33
+ }>;
34
+ /** Restore a soft-deleted lot. Rejects (409) if a live lot now uses the same number. */
35
+ function restore(collectionId: string, lotId: string): Promise<Lot>;
36
+ /** Re-resolve members from the current selector; returns the lot + a member diff. */
37
+ function resolve(collectionId: string, lotId: string): Promise<ResolveLotResponse>;
38
+ /** Paginated member product summaries. */
39
+ function listProducts(collectionId: string, lotId: string, opts?: {
40
+ page?: number;
41
+ limit?: number;
42
+ }): Promise<ListLotProductsResponse>;
43
+ function publicList(collectionId: string, params?: ListLotsParams): Promise<Lot[]>;
44
+ function publicGet(collectionId: string, lotId: string): Promise<Lot>;
45
+ function publicGetByNumber(collectionId: string, lotNumber: string): Promise<Lot>;
46
+ function publicListProducts(collectionId: string, lotId: string, opts?: {
47
+ page?: number;
48
+ limit?: number;
49
+ }): Promise<ListLotProductsResponse>;
50
+ }
@@ -0,0 +1,114 @@
1
+ // src/api/lots.ts
2
+ import { request, post, put, del } from "../http";
3
+ /**
4
+ * Lots — collection-scoped production groupings that span one or more products.
5
+ * Writes and admin reads hit `/admin/collection/:cid/lots`; the `public*` reads hit
6
+ * `/public/collection/:cid/lots` for cross-app consumers. Admin vs public is the path
7
+ * prefix (auth is the ambient bearer token); there is no `admin` flag in this SDK.
8
+ */
9
+ export var lots;
10
+ (function (lots) {
11
+ function adminBase(collectionId) {
12
+ return `/admin/collection/${encodeURIComponent(collectionId)}/lots`;
13
+ }
14
+ function publicBase(collectionId) {
15
+ return `/public/collection/${encodeURIComponent(collectionId)}/lots`;
16
+ }
17
+ function listQuery(params = {}) {
18
+ const qs = new URLSearchParams();
19
+ if (params.status)
20
+ qs.append('status', params.status);
21
+ if (params.search)
22
+ qs.append('search', params.search);
23
+ if (params.productId)
24
+ qs.append('productId', params.productId);
25
+ if (params.includeDeleted)
26
+ qs.append('includeDeleted', 'true');
27
+ const s = qs.toString();
28
+ return s ? `?${s}` : '';
29
+ }
30
+ function pageQuery(opts = {}) {
31
+ const qs = new URLSearchParams();
32
+ if (opts.page)
33
+ qs.append('page', String(opts.page));
34
+ if (opts.limit)
35
+ qs.append('limit', String(opts.limit));
36
+ const s = qs.toString();
37
+ return s ? `?${s}` : '';
38
+ }
39
+ // ── Admin (writes + admin reads) ──
40
+ /** Create a lot (resolves its selector into members). */
41
+ async function create(collectionId, lot) {
42
+ return post(adminBase(collectionId), lot);
43
+ }
44
+ lots.create = create;
45
+ /** List lots (summary rows; `payload`/`productIds` omitted). Filter by status, search, or containing productId. */
46
+ async function list(collectionId, params = {}) {
47
+ const res = await request(`${adminBase(collectionId)}${listQuery(params)}`);
48
+ return res.lots;
49
+ }
50
+ lots.list = list;
51
+ /** Get the full lot record. Pass `{ includeDeleted: true }` to fetch a soft-deleted one. */
52
+ async function get(collectionId, lotId, opts = {}) {
53
+ const qs = opts.includeDeleted ? '?includeDeleted=true' : '';
54
+ return request(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}${qs}`);
55
+ }
56
+ lots.get = get;
57
+ /** Look up a lot by its number (case-insensitive) — used by scan/resolver flows. */
58
+ async function getByNumber(collectionId, lotNumber, opts = {}) {
59
+ const qs = opts.includeDeleted ? '?includeDeleted=true' : '';
60
+ return request(`${adminBase(collectionId)}/by-number/${encodeURIComponent(lotNumber)}${qs}`);
61
+ }
62
+ lots.getByNumber = getByNumber;
63
+ /** Update a lot. Re-resolves members if the selector changed (response then carries `diff`). */
64
+ async function update(collectionId, lotId, lot) {
65
+ return put(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}`, lot);
66
+ }
67
+ lots.update = update;
68
+ /**
69
+ * Soft-delete a lot — recoverable, and frees its `lotNumber` for reuse. Distinct from
70
+ * {@link archive}. Hidden from reads unless `{ includeDeleted: true }`; undo with {@link restore}.
71
+ */
72
+ async function remove(collectionId, lotId) {
73
+ return del(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}`);
74
+ }
75
+ lots.remove = remove;
76
+ /** Archive a lot — a live lifecycle state (stays visible, keeps its number). Not a delete. */
77
+ async function archive(collectionId, lotId) {
78
+ return post(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}/archive`, {});
79
+ }
80
+ lots.archive = archive;
81
+ /** Restore a soft-deleted lot. Rejects (409) if a live lot now uses the same number. */
82
+ async function restore(collectionId, lotId) {
83
+ return post(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}/restore`, {});
84
+ }
85
+ lots.restore = restore;
86
+ /** Re-resolve members from the current selector; returns the lot + a member diff. */
87
+ async function resolve(collectionId, lotId) {
88
+ return post(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}/resolve`, {});
89
+ }
90
+ lots.resolve = resolve;
91
+ /** Paginated member product summaries. */
92
+ async function listProducts(collectionId, lotId, opts = {}) {
93
+ return request(`${adminBase(collectionId)}/${encodeURIComponent(lotId)}/products${pageQuery(opts)}`);
94
+ }
95
+ lots.listProducts = listProducts;
96
+ // ── Public (cross-app reads) ──
97
+ async function publicList(collectionId, params = {}) {
98
+ const res = await request(`${publicBase(collectionId)}${listQuery(params)}`);
99
+ return res.lots;
100
+ }
101
+ lots.publicList = publicList;
102
+ async function publicGet(collectionId, lotId) {
103
+ return request(`${publicBase(collectionId)}/${encodeURIComponent(lotId)}`);
104
+ }
105
+ lots.publicGet = publicGet;
106
+ async function publicGetByNumber(collectionId, lotNumber) {
107
+ return request(`${publicBase(collectionId)}/by-number/${encodeURIComponent(lotNumber)}`);
108
+ }
109
+ lots.publicGetByNumber = publicGetByNumber;
110
+ async function publicListProducts(collectionId, lotId, opts = {}) {
111
+ return request(`${publicBase(collectionId)}/${encodeURIComponent(lotId)}/products${pageQuery(opts)}`);
112
+ }
113
+ lots.publicListProducts = publicListProducts;
114
+ })(lots || (lots = {}));
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.15.24 | Generated: 2026-08-31T17:50:11.496Z
3
+ Version: 1.16.1 | Generated: 2026-09-01T15:57:41.258Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -35,6 +35,7 @@ For detailed guides on specific features:
35
35
  - **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
36
36
  - **[Proof Share Grants](proof-share-grants.md)** - Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner links)
37
37
  - **[Proof Ownership Transfer](proof-ownership-transfer.md)** - Moving a proof to a new owner: directed transfer, open release, accept/cancel, and the state machine
38
+ - **[Lots](lots.md)** - Collection-scoped production groupings spanning many SKUs; facet/product selectors, member resolution, and GS1 AI(10) batch-then-lot resolution
38
39
  - **[Item Context](item-context.md)** - The `itemContext` container prop derived from a serial-proof URL or NFC tap (what item the URL points at)
39
40
  - **[Product Facets SDK](PRODUCT_FACETS_SDK.md)** - Admin and public product facet endpoints and TypeScript interfaces
40
41
  - **[Attestations](attestations.md)** - Append-only fact log with cryptographic chain integrity, time-series analytics, and public/owner/admin visibility
@@ -135,6 +136,7 @@ The Smartlinks SDK is organized into the following namespaces:
135
136
  - **jobs** - Functions for jobs operations
136
137
  - **journeysAnalytics** - Functions for journeysAnalytics operations
137
138
  - **location** - Functions for location operations
139
+ - **lots** - Functions for lots operations
138
140
  - **navigation** - Functions for navigation operations
139
141
  - **order** - Functions for order operations
140
142
  - **products** - Functions for products operations
@@ -3056,6 +3058,63 @@ interface AccountInfoResponse {
3056
3058
 
3057
3059
  ### authKit
3058
3060
 
3061
+ **AuthTelemetryEvent** (interface)
3062
+ ```typescript
3063
+ interface AuthTelemetryEvent {
3064
+ eventId: string
3065
+ correlationId: string
3066
+ clientId: string
3067
+ collectionId?: string
3068
+ type: AuthEventType
3069
+ flow: AuthFlow
3070
+ ts: string
3071
+ durationMs?: number
3072
+ outcome?: 'success' | 'error' | 'stalled' | 'abandoned'
3073
+ error?: {
3074
+ code?: string
3075
+ statusCode?: number
3076
+ message?: string
3077
+ name?: string
3078
+ stack?: string
3079
+ endpoint?: string
3080
+ }
3081
+ context: {
3082
+ sdkVersion: string
3083
+ authKitVersion: string
3084
+ mode: 'standalone' | 'embedded' | 'proxy' | 'native'
3085
+ route?: string
3086
+ deepLinkMode?: string
3087
+ userAgent: string
3088
+ platform?: string
3089
+ language?: string
3090
+ online: boolean
3091
+ viewport?: { w: number; h: number }
3092
+ darkMode?: boolean
3093
+ }
3094
+ subject?: { uid?: string; emailHash?: string }
3095
+ }
3096
+ ```
3097
+
3098
+ **TelemetryIngestResponse** (interface)
3099
+ ```typescript
3100
+ interface TelemetryIngestResponse {
3101
+ accepted: number
3102
+ rejected: number
3103
+ rejectedIds: string[]
3104
+ }
3105
+ ```
3106
+
3107
+ **AuthKitTelemetryConfig** (interface)
3108
+ ```typescript
3109
+ interface AuthKitTelemetryConfig {
3110
+ enabled?: boolean
3111
+ successSampleRate?: number
3112
+ captureJsErrors?: boolean
3113
+ stallThresholdMs?: number
3114
+ retentionDays?: number
3115
+ }
3116
+ ```
3117
+
3059
3118
  **AuthKitUser** (interface)
3060
3119
  ```typescript
3061
3120
  interface AuthKitUser {
@@ -3519,6 +3578,10 @@ interface AuthKitLockoutPolicy {
3519
3578
  }
3520
3579
  ```
3521
3580
 
3581
+ **AuthEventType** = ``
3582
+
3583
+ **AuthFlow** = ``
3584
+
3522
3585
  **RefreshErrorCode** = ``
3523
3586
 
3524
3587
  **AuthKitErrorCode** = ``
@@ -6355,6 +6418,122 @@ interface LocationSearchResponse {
6355
6418
 
6356
6419
  **LocationPayload** = `Omit<`
6357
6420
 
6421
+ ### lots
6422
+
6423
+ **LotPayload** (interface)
6424
+ ```typescript
6425
+ interface LotPayload {
6426
+ manufacturedAt?: string | null
6427
+ expiresAt?: string | null
6428
+ location?: string | null
6429
+ custom?: Record<string, any>
6430
+ [key: string]: any
6431
+ }
6432
+ ```
6433
+
6434
+ **Lot** (interface)
6435
+ ```typescript
6436
+ interface Lot {
6437
+ id: string
6438
+ collectionId: string
6439
+ lotNumber: string
6440
+ name?: string | null
6441
+ description?: string | null
6442
+ status: LotStatus
6443
+ selector: LotSelector
6444
+ payload?: LotPayload
6445
+ destination?: Record<string, any> | null
6446
+ productCount: number
6447
+ productIds?: string[]
6448
+ resolvedAt?: string | null
6449
+ createdBy?: string | null
6450
+ updatedBy?: string | null
6451
+ createdAt: string
6452
+ updatedAt: string
6453
+ deletedAt?: string | null
6454
+ }
6455
+ ```
6456
+
6457
+ **LotCreateInput** (interface)
6458
+ ```typescript
6459
+ interface LotCreateInput {
6460
+ lotNumber: string
6461
+ name?: string
6462
+ description?: string
6463
+ selector?: LotSelector
6464
+ payload?: LotPayload
6465
+ destination?: Record<string, any> | null
6466
+ status?: LotStatus
6467
+ id?: string
6468
+ }
6469
+ ```
6470
+
6471
+ **ListLotsParams** (interface)
6472
+ ```typescript
6473
+ interface ListLotsParams {
6474
+ status?: LotStatus
6475
+ search?: string
6476
+ productId?: string
6477
+ includeDeleted?: boolean
6478
+ }
6479
+ ```
6480
+
6481
+ **ListLotsResponse** (interface)
6482
+ ```typescript
6483
+ interface ListLotsResponse {
6484
+ lots: Lot[]
6485
+ }
6486
+ ```
6487
+
6488
+ **LotMemberDiff** (interface)
6489
+ ```typescript
6490
+ interface LotMemberDiff {
6491
+ added: string[]; removed: string[]
6492
+ }
6493
+ ```
6494
+
6495
+ **ResolveLotResponse** (interface)
6496
+ ```typescript
6497
+ interface ResolveLotResponse {
6498
+ lot: Lot; diff: LotMemberDiff
6499
+ }
6500
+ ```
6501
+
6502
+ **LotProductSummary** (interface)
6503
+ ```typescript
6504
+ interface LotProductSummary {
6505
+ id: string; name?: string; gtin?: string | null
6506
+ }
6507
+ ```
6508
+
6509
+ **ListLotProductsResponse** (interface)
6510
+ ```typescript
6511
+ interface ListLotProductsResponse {
6512
+ products: LotProductSummary[]
6513
+ total: number
6514
+ page: number
6515
+ limit: number
6516
+ }
6517
+ ```
6518
+
6519
+ **LotResolutionResult** (interface)
6520
+ ```typescript
6521
+ interface LotResolutionResult {
6522
+ match: 'batch' | 'lot' | 'product' | 'none'
6523
+ productId: string
6524
+ batchId: string | null
6525
+ lotId: string | null
6526
+ ai10: string | null
6527
+ destination?: any
6528
+ }
6529
+ ```
6530
+
6531
+ **LotStatus** = `'open' | 'closed' | 'recalled' | 'archived'`
6532
+
6533
+ **LotSelector** = ``
6534
+
6535
+ **LotUpdateInput** = `Partial<LotCreateInput>`
6536
+
6358
6537
  ### loyalty
6359
6538
 
6360
6539
  **LoyaltyScheme** (interface)
@@ -7329,6 +7508,9 @@ interface Proof {
7329
7508
  productId: string
7330
7509
  tokenId: string
7331
7510
  userId: string
7511
+ batchId?: string | null
7512
+ variantId?: string | null
7513
+ lotId?: string | null
7332
7514
  claimable?: boolean
7333
7515
  virtual?: boolean
7334
7516
  data?: Record<string, JsonValue>
@@ -9771,6 +9953,50 @@ Public: Fetch a global location by ID GET /public/location/:locationId
9771
9953
  locationId: string) → `Promise<Location>`
9772
9954
  Public: Fetch a location for a collection; returns either a collection-owned or global fallback GET /public/collection/:collectionId/location/:locationId
9773
9955
 
9956
+ ### lots
9957
+
9958
+ **create**(collectionId: string, lot: LotCreateInput) → `Promise<Lot>`
9959
+ Create a lot (resolves its selector into members).
9960
+
9961
+ **list**(collectionId: string, params: ListLotsParams = {}) → `Promise<Lot[]>`
9962
+ List lots (summary rows; `payload`/`productIds` omitted). Filter by status, search, or containing productId.
9963
+
9964
+ **get**(collectionId: string, lotId: string, opts: { includeDeleted?: boolean } = {}) → `Promise<Lot>`
9965
+ Get the full lot record. Pass `{ includeDeleted: true }` to fetch a soft-deleted one.
9966
+
9967
+ **getByNumber**(collectionId: string, lotNumber: string, opts: { includeDeleted?: boolean } = {}) → `Promise<Lot>`
9968
+ Look up a lot by its number (case-insensitive) — used by scan/resolver flows.
9969
+
9970
+ **update**(collectionId: string, lotId: string, lot: LotUpdateInput) → `Promise<Lot>`
9971
+ Update a lot. Re-resolves members if the selector changed (response then carries `diff`).
9972
+
9973
+ **remove**(collectionId: string, lotId: string) → `Promise<`
9974
+ Soft-delete a lot — recoverable, and frees its `lotNumber` for reuse. Distinct from {@link archive}. Hidden from reads unless `{ includeDeleted: true }`; undo with {@link restore}.
9975
+
9976
+ **archive**(collectionId: string, lotId: string) → `Promise<`
9977
+ Archive a lot — a live lifecycle state (stays visible, keeps its number). Not a delete.
9978
+
9979
+ **restore**(collectionId: string, lotId: string) → `Promise<Lot>`
9980
+ Restore a soft-deleted lot. Rejects (409) if a live lot now uses the same number.
9981
+
9982
+ **resolve**(collectionId: string, lotId: string) → `Promise<ResolveLotResponse>`
9983
+ Re-resolve members from the current selector; returns the lot + a member diff.
9984
+
9985
+ **listProducts**(collectionId: string, lotId: string, opts: { page?: number; limit?: number } = {}) → `Promise<ListLotProductsResponse>`
9986
+ Paginated member product summaries.
9987
+
9988
+ **publicList**(collectionId: string, params: ListLotsParams = {}) → `Promise<Lot[]>`
9989
+ Paginated member product summaries.
9990
+
9991
+ **publicGet**(collectionId: string, lotId: string) → `Promise<Lot>`
9992
+ Paginated member product summaries.
9993
+
9994
+ **publicGetByNumber**(collectionId: string, lotNumber: string) → `Promise<Lot>`
9995
+ Paginated member product summaries.
9996
+
9997
+ **publicListProducts**(collectionId: string, lotId: string, opts: { page?: number; limit?: number } = {}) → `Promise<ListLotProductsResponse>`
9998
+ Paginated member product summaries.
9999
+
9774
10000
  ### loyalty
9775
10001
 
9776
10002
  Loyalty programmes built on top of collections. Configure schemes and earning rules; contacts earn points automatically via interaction events. See the [Loyalty guide](loyalty.md) for the full walkthrough.
@@ -298,8 +298,9 @@ about:
298
298
  | `hub` | The Hub module. |
299
299
  | `portal` | QR code scan portals. |
300
300
  | `virtualItems` | Virtual items — algorithmic per-item IDs with no persistent row (battery serials, scan-to-collect points, bulk QR sheets). Replaces the old root-level `virtualItemsEnabled` boolean, which has been removed (§8) — resolve this like any other feature flag, not as a separate config key. Independent of `itemRecordMode`. |
301
- | `batches` | Batch support. Used to be the `Collection.batches` boolean; that field has been removed — this flag is now the only source of truth. |
301
+ | `batches` | Batch support. Used to be the `Collection.batches` boolean; that field has been removed — this flag is now the only source of truth. Also gates AI(10) **batch** resolution on a GS1 Digital Link scan (§ see `lots.md`). |
302
302
  | `variants` | Variant support. Used to be the `Collection.variants` boolean; that field has been removed — this flag is now the only source of truth. |
303
+ | `lots` | Lot support — collection-scoped production groupings spanning many SKUs (see `lots.md`). Gates AI(10) **lot** resolution on a GS1 Digital Link scan. Independent of `batches`; when both are on, a scanned AI(10) value resolves batch-first (specific SKU) then lot (broad). |
303
304
 
304
305
  Same resolution rule as any flag (§4.4) — don't read these off
305
306
  `cfg.system.features` directly, always go through `isFeatureEnabled()`.
@@ -0,0 +1,111 @@
1
+ # Lots
2
+
3
+ A **Lot** is a collection-scoped production grouping that spans one or more products
4
+ (SKUs) — a single identifier applied across many SKUs and/or many production runs. It's
5
+ the right tool when a manufacturer wants one lot number (e.g. `LOT-2026-09`) across a whole
6
+ range, rather than a per-product **batch** (a single run of a single product).
7
+
8
+ Lots are a first-class entity (not app records): cross-app readable, admin-written, with a
9
+ real lifecycle. They are **never fanned out into batches** — the lot is the single source of
10
+ truth for its shared data.
11
+
12
+ ---
13
+
14
+ ## Concepts
15
+
16
+ - **Selector** — how member products are matched. Two modes:
17
+ - `{ mode: 'facets', rules: [{ key, values }] }` — AND across rules, OR within a rule's values (resolved against the facet index, so it scales to thousands of SKUs).
18
+ - `{ mode: 'products', productIds: [...] }` — an explicit list.
19
+ - **`productIds` / `productCount`** — the materialised snapshot of resolved members (re-resolved on create, on selector change, and on demand via `resolve`).
20
+ - **`payload`** — shared lot data (dates, supplier ref, custom fields). Lives only on the lot.
21
+ - **`status`** — `open` → `closed` → `recalled` → `archived`. A live lifecycle state, distinct from deletion.
22
+ - **`destination`** — optional lot-level redirect; wins over the product's on a lot-scoped scan.
23
+
24
+ ### Archive vs delete
25
+
26
+ Two separate ideas — mirroring the platform's `deletedAt` convention:
27
+
28
+ | | `archive` (`status: 'archived'`) | `remove` (soft-delete, `deletedAt`) |
29
+ |---|---|---|
30
+ | Record | stays **live** & visible | **hidden** from reads unless `includeDeleted: true` |
31
+ | `lotNumber` | **stays reserved** | **freed** for reuse by a new lot |
32
+ | AI(10) scan | not resolved (excluded) | not resolved |
33
+ | Reversible | change status back | `restore` (409 if the number was taken by a live lot) |
34
+
35
+ Nothing is ever hard-deleted (joins/history stay intact). Use **archive** for "this run is done, keep it around"; use **remove** for "this was a mistake, and I want the number back."
36
+
37
+ ---
38
+
39
+ ## SDK — `SL.lots.*`
40
+
41
+ Writes and admin reads hit `/admin/collection/:cid/lots`; the `public*` reads hit
42
+ `/public/collection/:cid/lots` for cross-app consumers (auth is the ambient bearer token —
43
+ there's no `admin` flag).
44
+
45
+ ```ts
46
+ import { lots } from '@proveanything/smartlinks'
47
+
48
+ // Create — facet-targeted lot
49
+ const lot = await lots.create(collectionId, {
50
+ lotNumber: 'LOT-2026-09',
51
+ name: 'September Oak run',
52
+ selector: { mode: 'facets', rules: [
53
+ { key: 'supplier', values: ['Acme Timber'] },
54
+ { key: 'range', values: ['Oslo', 'Bergen'] },
55
+ ]},
56
+ payload: { manufacturedAt: '2026-09-01', custom: { supplierBatchRef: 'ACM-7741' } },
57
+ })
58
+
59
+ const list = await lots.list(collectionId, { status: 'open' })
60
+ const byId = await lots.get(collectionId, lot.id)
61
+ const byNumber = await lots.getByNumber(collectionId, 'LOT-2026-09') // case-insensitive
62
+ const containing = await lots.list(collectionId, { productId: 'prd_abc' }) // reverse lookup
63
+ const updated = await lots.update(collectionId, lot.id, { status: 'closed' })
64
+ const { diff } = await lots.resolve(collectionId, lot.id) // { added, removed }
65
+ const members = await lots.listProducts(collectionId, lot.id, { page: 1, limit: 50 })
66
+
67
+ await lots.archive(collectionId, lot.id) // live, keeps its number
68
+ await lots.remove(collectionId, lot.id) // soft-delete, frees the number
69
+ const withDeleted = await lots.list(collectionId, { includeDeleted: true })
70
+ const restored = await lots.restore(collectionId, lot.id) // undo a soft-delete
71
+
72
+ // Cross-app reads
73
+ const publicLots = await lots.publicList(collectionId)
74
+ ```
75
+
76
+ Exported types: `Lot`, `LotStatus`, `LotSelector`, `LotPayload`, `LotCreateInput`,
77
+ `LotUpdateInput`, `ListLotsParams`, `ResolveLotResponse`, `ListLotProductsResponse`,
78
+ `LotResolutionResult`.
79
+
80
+ ---
81
+
82
+ ## GS1 Digital Link resolution — AI(10)
83
+
84
+ GS1 gives batch and lot a **single** slot: AI(10) ("Batch or Lot Number"), in a Digital Link
85
+ as `/01/{gtin}/10/{value}`. A physical code carries exactly one value there, so the server
86
+ decides which namespace it belongs to, driven by two **feature flags** — `batches` and `lots`
87
+ — in the platform feature-flag system (`appConfig.system.features`), resolved the standard way
88
+ (`appConfiguration.isFeatureEnabled(collectionId, 'lots')`):
89
+
90
+ - Explicit `true`/`false` in `system.features` wins; otherwise **enterprise** accounts default
91
+ a flag on and everyone else defaults off. So AI(10) batch/lot resolution is opt-in — no
92
+ existing scan changes until `batches`/`lots` is enabled for the collection.
93
+
94
+ Resolution order — **batch first, then lot** (a hierarchy, not a collision):
95
+
96
+ 1. **Batch** is the narrowest scope (one product). If the scanned product has a batch whose id
97
+ or name matches the AI(10) value, it wins — a batch is a **specific-SKU override**.
98
+ 2. **Lot** is broad (many products). If no batch matches and the value is a lot number *and the
99
+ scanned product is a member*, the lot resolves.
100
+ 3. Otherwise it resolves at the product level.
101
+
102
+ This means a range can share one lot code, and a single SKU can be given richer/overriding
103
+ detail by creating a batch with the **same** identifier — the batch simply takes precedence for
104
+ that SKU. A recalled lot (`status: 'recalled'`) still resolves so the destination page can show
105
+ a recall notice (a `&recall=1` context param is added).
106
+
107
+ The server returns a typed shape ({@link LotResolutionResult}); the front end renders it — clients
108
+ should not implement their own fallback.
109
+
110
+ See also [proof-product-data-scoping.md](proof-product-data-scoping.md) and the GS1 link
111
+ generator in [utils.md](utils.md) (`buildGs1DigitalLink`).
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export type { LoginResponse, VerifyTokenResponse, AccountInfoResponse, AuthLocat
10
10
  export type { UserAccountRegistrationRequest, } from "./types/auth";
11
11
  export type { CommunicationEvent, CommsQueryByUser, CommsRecipientIdsQuery, CommsRecipientsWithoutActionQuery, CommsRecipientsWithActionQuery, RecipientId, RecipientWithOutcome, LogCommunicationEventBody, LogBulkCommunicationEventsBody, AppendResult, AppendBulkResult, CommsSettings, TopicConfig, CommsSettingsGetResponse, CommsSettingsPatchBody, CommsPublicTopicsResponse, UnsubscribeQuery, UnsubscribeResponse, CommsConsentUpsertRequest, CommsPreferencesUpsertRequest, CommsSubscribeRequest, CommsSubscribeResponse, CommsSubscriptionCheckQuery, CommsSubscriptionCheckResponse, CommsListMethodsQuery, CommsListMethodsResponse, RegisterEmailMethodRequest, RegisterSmsMethodRequest, RegisterMethodResponse, SubscriptionsResolveRequest, SubscriptionsResolveResponse, } from "./types/comms";
12
12
  export type { BatchResponse, BatchCreateRequest, BatchUpdateRequest, } from "./types/batch";
13
+ export type { Lot, LotStatus, LotSelector, LotPayload, LotCreateInput, LotUpdateInput, ListLotsParams, ListLotsResponse, LotMemberDiff, ResolveLotResponse, LotProductSummary, ListLotProductsResponse, LotResolutionResult, } from "./types/lots";
13
14
  export type { VariantResponse, VariantCreateRequest, VariantUpdateRequest, } from "./types/variant";
14
15
  export type { BroadcastSendRequest } from "./types/broadcasts";
15
16
  export type { AppConfigOptions } from "./api/appConfiguration";