@proveanything/smartlinks 1.7.0 → 1.7.2

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/tags.js CHANGED
@@ -3,8 +3,17 @@ import { request, post, put, del } from "../http";
3
3
  /**
4
4
  * Tag Management API
5
5
  *
6
- * Create mappings between physical tags (NFC tags, QR codes, etc.) and digital proofs.
7
- * Supports automatic serial number generation, batch operations, and public lookups.
6
+ * Manages mappings between physical tag identifiers (NFC UIDs, QR codes, barcodes,
7
+ * etc.) and products, proofs, or any polymorphic object in the platform.
8
+ *
9
+ * ### Two-tier architecture
10
+ * - **Per-org shard** (`tags` table) — full tag data; used for all collection-scoped
11
+ * queries.
12
+ * - **Shared shard** (`tag_index` table) — `tagId → collectionId` routing only; used
13
+ * when the collection is not yet known.
14
+ *
15
+ * 99% of callers will know the collection and should use collection-scoped endpoints
16
+ * directly. Use `resolveTag` only when you have a raw `tagId` and no collection context.
8
17
  */
9
18
  export var tags;
10
19
  (function (tags) {
@@ -12,28 +21,30 @@ export var tags;
12
21
  // Admin Endpoints
13
22
  // ============================================================================
14
23
  /**
15
- * Create a single tag mapping.
16
- * If proofId is not provided, automatically generates a serial number.
24
+ * Create a single tag mapping (admin).
17
25
  *
18
- * @param collectionId - Identifier of the parent collection
19
- * @param data - Tag creation data
20
- * @returns Promise resolving to a CreateTagResponse object
21
- * @throws ErrorResponse if the request fails
26
+ * If `productId` is set without `proofId`, a serial number is auto-generated
27
+ * unless `useSerialNumber: true` is explicitly passed.
28
+ * `refType` and `refId` can be set independently of or alongside product/proof.
29
+ *
30
+ * @param collectionId - Collection context
31
+ * @param data - Tag creation payload; `tagId` is required
32
+ * @returns The created tag (`wasUpdated: true` when `force=true` triggered an update)
22
33
  *
23
34
  * @example
24
35
  * ```typescript
25
36
  * // Auto-generate serial number
26
37
  * const tag = await tags.create('coll_123', {
27
- * tagId: 'TAG001',
38
+ * tagId: 'NFC-001',
28
39
  * productId: 'prod_456',
29
- * variantId: 'var_789'
40
+ * batchId: 'batch_2026_01',
30
41
  * })
31
42
  *
32
- * // Use explicit proof ID
43
+ * // Explicit proof + polymorphic ref
33
44
  * const tag2 = await tags.create('coll_123', {
34
- * tagId: 'TAG002',
35
- * productId: 'prod_456',
36
- * proofId: 'proof_explicit_123'
45
+ * tagId: 'NFC-002',
46
+ * refType: 'container',
47
+ * refId: 'container-uuid',
37
48
  * })
38
49
  * ```
39
50
  */
@@ -43,27 +54,25 @@ export var tags;
43
54
  }
44
55
  tags.create = create;
45
56
  /**
46
- * Create multiple tag mappings efficiently in a batch operation.
47
- * By default, auto-generates serial numbers for all tags without explicit proofId.
48
- * Tags are grouped by product/variant/batch and serial numbers are generated in
49
- * a single transaction per group for optimal performance.
57
+ * Batch-create tags (admin).
50
58
  *
51
- * @param collectionId - Identifier of the parent collection
52
- * @param data - Batch creation data
53
- * @returns Promise resolving to a CreateTagsBatchResponse with summary and results
54
- * @throws ErrorResponse if the request fails
59
+ * Tags with `productId` but no `proofId` automatically get serial numbers.
60
+ * Serial number generation is grouped by `(productId, variantId, batchId)` for
61
+ * efficiency. Partial success is possible — check `results` for individual outcomes.
62
+ *
63
+ * @param collectionId - Collection context
64
+ * @param data - Batch payload; `force` applies to all tags in the batch
65
+ * @returns `BatchCreateResult` with summary and per-tag outcomes
55
66
  *
56
67
  * @example
57
68
  * ```typescript
58
69
  * const result = await tags.createBatch('coll_123', {
59
70
  * tags: [
60
- * { tagId: 'TAG001', productId: 'prod_456', variantId: 'var_789' },
61
- * { tagId: 'TAG002', productId: 'prod_456', variantId: 'var_789' },
62
- * { tagId: 'TAG003', productId: 'prod_456', batchId: 'batch_100' }
63
- * ]
71
+ * { tagId: 'NFC-001', productId: 'prod_456', batchId: 'batch_2026_01' },
72
+ * { tagId: 'NFC-002', productId: 'prod_456', batchId: 'batch_2026_01' },
73
+ * ],
64
74
  * })
65
- *
66
- * console.log(`Created: ${result.summary.created}, Failed: ${result.summary.failed}`)
75
+ * console.log(`Created: ${result.summary.created}, Conflicts: ${result.summary.conflicts}`)
67
76
  * ```
68
77
  */
69
78
  async function createBatch(collectionId, data) {
@@ -72,20 +81,38 @@ export var tags;
72
81
  }
73
82
  tags.createBatch = createBatch;
74
83
  /**
75
- * Update an existing tag mapping.
84
+ * Get a single tag by `tagId` (admin).
85
+ *
86
+ * @param collectionId - Collection context
87
+ * @param tagId - Physical tag identifier
88
+ * @returns Full `Tag` record
89
+ */
90
+ async function get(collectionId, tagId) {
91
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags/${encodeURIComponent(tagId)}`;
92
+ return request(path);
93
+ }
94
+ tags.get = get;
95
+ /**
96
+ * Update a tag (admin).
76
97
  *
77
- * @param collectionId - Identifier of the parent collection
78
- * @param tagId - Unique tag identifier
79
- * @param data - Update data (only include fields to update)
80
- * @returns Promise resolving to an UpdateTagResponse object
81
- * @throws ErrorResponse if the request fails
98
+ * Partial update — only provided fields are changed. `metadata` is
99
+ * deep-merged with the existing value. Pass `refType: null, refId: null`
100
+ * to clear the polymorphic ref.
101
+ *
102
+ * @param collectionId - Collection context
103
+ * @param tagId - Physical tag identifier
104
+ * @param data - Fields to update
105
+ * @returns Updated `Tag`
82
106
  *
83
107
  * @example
84
108
  * ```typescript
85
- * const updated = await tags.update('coll_123', 'TAG001', {
86
- * variantId: 'var_999',
87
- * metadata: { notes: 'Updated variant' }
109
+ * const updated = await tags.update('coll_123', 'NFC-001', {
110
+ * variantId: 'var_premium',
111
+ * metadata: { notes: 'Updated to premium variant' },
88
112
  * })
113
+ *
114
+ * // Clear polymorphic ref
115
+ * await tags.update('coll_123', 'NFC-001', { refType: null, refId: null })
89
116
  * ```
90
117
  */
91
118
  async function update(collectionId, tagId, data) {
@@ -94,17 +121,13 @@ export var tags;
94
121
  }
95
122
  tags.update = update;
96
123
  /**
97
- * Delete a tag mapping.
124
+ * Delete a tag (admin).
98
125
  *
99
- * @param collectionId - Identifier of the parent collection
100
- * @param tagId - Unique tag identifier
101
- * @returns Promise resolving to a DeleteTagResponse object
102
- * @throws ErrorResponse if the request fails
126
+ * Permanently removes the tag from the per-org shard and the shared index.
103
127
  *
104
- * @example
105
- * ```typescript
106
- * await tags.remove('coll_123', 'TAG001')
107
- * ```
128
+ * @param collectionId - Collection context
129
+ * @param tagId - Physical tag identifier
130
+ * @returns `{ success: true }`
108
131
  */
109
132
  async function remove(collectionId, tagId) {
110
133
  const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags/${encodeURIComponent(tagId)}`;
@@ -112,73 +135,52 @@ export var tags;
112
135
  }
113
136
  tags.remove = remove;
114
137
  /**
115
- * Get a single tag mapping by tagId.
116
- *
117
- * @param collectionId - Identifier of the parent collection
118
- * @param tagId - Unique tag identifier
119
- * @returns Promise resolving to a GetTagResponse object
120
- * @throws ErrorResponse if the request fails
121
- *
122
- * @example
123
- * ```typescript
124
- * const tag = await tags.get('coll_123', 'TAG001')
125
- * ```
126
- */
127
- async function get(collectionId, tagId) {
128
- const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags/${encodeURIComponent(tagId)}`;
129
- return request(path);
130
- }
131
- tags.get = get;
132
- /**
133
- * List all tags for a collection with optional filters and pagination.
138
+ * List tags with optional filters and pagination (admin).
134
139
  *
135
- * @param collectionId - Identifier of the parent collection
136
- * @param params - Optional query parameters for filtering and pagination
137
- * @returns Promise resolving to a ListTagsResponse object
138
- * @throws ErrorResponse if the request fails
140
+ * @param collectionId - Collection context
141
+ * @param params - Optional filter and pagination params
142
+ * @returns `{ tags: Tag[], limit: number, offset: number }`
139
143
  *
140
144
  * @example
141
145
  * ```typescript
142
- * // List all tags
143
- * const all = await tags.list('coll_123')
146
+ * // All tags for a product
147
+ * const { tags: list } = await tags.list('coll_123', { productId: 'prod_456' })
144
148
  *
145
- * // List with filters
146
- * const filtered = await tags.list('coll_123', {
147
- * productId: 'prod_456',
148
- * variantId: 'var_789',
149
- * limit: 50,
150
- * offset: 0
149
+ * // All tags linked to a container
150
+ * const { tags: linked } = await tags.list('coll_123', {
151
+ * refType: 'container',
152
+ * refId: 'container-uuid',
151
153
  * })
152
154
  * ```
153
155
  */
154
156
  async function list(collectionId, params) {
155
- const queryParams = new URLSearchParams();
157
+ const q = new URLSearchParams();
156
158
  if (params === null || params === void 0 ? void 0 : params.limit)
157
- queryParams.append('limit', params.limit.toString());
159
+ q.append('limit', params.limit.toString());
158
160
  if (params === null || params === void 0 ? void 0 : params.offset)
159
- queryParams.append('offset', params.offset.toString());
161
+ q.append('offset', params.offset.toString());
160
162
  if (params === null || params === void 0 ? void 0 : params.productId)
161
- queryParams.append('productId', params.productId);
163
+ q.append('productId', params.productId);
162
164
  if (params === null || params === void 0 ? void 0 : params.variantId)
163
- queryParams.append('variantId', params.variantId);
165
+ q.append('variantId', params.variantId);
164
166
  if (params === null || params === void 0 ? void 0 : params.batchId)
165
- queryParams.append('batchId', params.batchId);
167
+ q.append('batchId', params.batchId);
166
168
  if (params === null || params === void 0 ? void 0 : params.refType)
167
- queryParams.append('refType', params.refType);
169
+ q.append('refType', params.refType);
168
170
  if (params === null || params === void 0 ? void 0 : params.refId)
169
- queryParams.append('refId', params.refId);
170
- const query = queryParams.toString();
171
- const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags${query ? `?${query}` : ''}`;
171
+ q.append('refId', params.refId);
172
+ const qs = q.toString();
173
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags${qs ? `?${qs}` : ''}`;
172
174
  return request(path);
173
175
  }
174
176
  tags.list = list;
175
177
  /**
176
- * Reverse lookup find all tags linked to a given app object (admin).
178
+ * Reverse lookup — find all tags linked to a given object (admin).
177
179
  *
178
- * Uses a global cross-shard index keyed on `(orgId, refType, refId)`, so it
179
- * is safe to call without knowing which collection the object belongs to.
180
+ * Uses a compound index on `(orgId, refType, refId)` on the per-org shard.
181
+ * No embed support on the admin side.
180
182
  *
181
- * @param collectionId - Collection context (used for auth scope)
183
+ * @param collectionId - Collection context
182
184
  * @param params - `refType` and `refId` are required
183
185
  * @returns `{ tags: Tag[] }`
184
186
  *
@@ -191,126 +193,163 @@ export var tags;
191
193
  * ```
192
194
  */
193
195
  async function byRef(collectionId, params) {
194
- const queryParams = new URLSearchParams();
195
- queryParams.append('refType', params.refType);
196
- queryParams.append('refId', params.refId);
197
- const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags/by-ref?${queryParams.toString()}`;
196
+ const q = new URLSearchParams();
197
+ q.append('refType', params.refType);
198
+ q.append('refId', params.refId);
199
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags/by-ref?${q.toString()}`;
198
200
  return request(path);
199
201
  }
200
202
  tags.byRef = byRef;
201
203
  // ============================================================================
202
- // Public Endpoints
204
+ // Public Endpoints — global resolve (collection unknown)
203
205
  // ============================================================================
204
206
  /**
205
- * Public lookup of a single tag by tagId (global).
206
- * Optionally embed related collection, product, or proof data.
207
- * No authentication required.
207
+ * Global tag resolve — returns `{ tagId, collectionId }` only.
208
208
  *
209
- * @param tagId - Unique tag identifier (globally unique)
210
- * @param params - Optional parameters (embed)
211
- * @returns Promise resolving to a PublicGetTagResponse with optional embedded data
212
- * @throws ErrorResponse if the request fails
209
+ * Use this **only** when you have a raw `tagId` and do not yet know which
210
+ * collection it belongs to. Queries the shared `tag_index` shard.
211
+ * Once `collectionId` is resolved, call `publicGetByCollection` for full data.
212
+ *
213
+ * > The global `/public/tags/by-ref` endpoint has been removed.
214
+ * > Use the collection-scoped `publicByRef` instead.
215
+ *
216
+ * @param tagId - Physical tag identifier
217
+ * @returns `{ tagId, collectionId }` — routing info only, no full tag data
213
218
  *
214
219
  * @example
215
220
  * ```typescript
216
- * // Simple lookup
217
- * const result = await tags.getTag('TAG001')
221
+ * // Step 1: resolve collection
222
+ * const { collectionId } = await tags.resolveTag('NFC-001')
218
223
  *
219
- * // With embedded data
220
- * const withData = await tags.getTag('TAG001', {
221
- * embed: 'collection,product,proof'
222
- * })
223
- * console.log(withData.tag, withData.collection, withData.product, withData.proof)
224
+ * // Step 2: full lookup with embedded data
225
+ * const { tag, embedded } = await tags.publicGetByCollection(
226
+ * collectionId, 'NFC-001', 'product,proof'
227
+ * )
224
228
  * ```
225
229
  */
226
- async function getTag(tagId, params) {
227
- const queryParams = new URLSearchParams();
228
- if (params === null || params === void 0 ? void 0 : params.embed)
229
- queryParams.append('embed', params.embed);
230
- const query = queryParams.toString();
231
- const path = `/public/tags/${encodeURIComponent(tagId)}${query ? `?${query}` : ''}`;
230
+ async function resolveTag(tagId) {
231
+ const path = `/public/tags/${encodeURIComponent(tagId)}`;
232
232
  return request(path);
233
233
  }
234
- tags.getTag = getTag;
234
+ tags.resolveTag = resolveTag;
235
+ // ============================================================================
236
+ // Public Endpoints — collection-scoped
237
+ // ============================================================================
235
238
  /**
236
- * Backward-compat: Public lookup with collectionId parameter (ignored).
237
- * Calls global route under /public/tags/:tagId.
239
+ * Single tag lookup with optional embedded data (public).
240
+ *
241
+ * `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref`
242
+ *
243
+ * Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'`
244
+ * (`'collection'` is not supported — the collection is already known from the URL).
245
+ *
246
+ * @param collectionId - Collection context
247
+ * @param tagId - Physical tag identifier
248
+ * @param embed - Optional comma-separated embed string
249
+ * @returns `{ tag: Tag, embedded: TagEmbedded }`
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * const { tag, embedded } = await tags.publicGetByCollection(
254
+ * 'coll_123', 'NFC-001', 'product,proof'
255
+ * )
256
+ * const product = embedded.products?.[tag.productId!]
257
+ * const proof = embedded.proofs?.[tag.proofId!]
258
+ * ```
238
259
  */
239
- async function publicGet(_collectionId, tagId, params) {
240
- return getTag(tagId, params);
260
+ async function publicGetByCollection(collectionId, tagId, embed) {
261
+ const q = new URLSearchParams();
262
+ if (embed)
263
+ q.append('embed', embed);
264
+ const qs = q.toString();
265
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/tags/${encodeURIComponent(tagId)}${qs ? `?${qs}` : ''}`;
266
+ return request(path);
241
267
  }
242
- tags.publicGet = publicGet;
268
+ tags.publicGetByCollection = publicGetByCollection;
243
269
  /**
244
- * Public batch lookup of multiple tags in a single request (POST).
245
- * Only returns tags from the specified collection.
246
- * Optionally embed related data. Related data is deduplicated and batch-fetched.
247
- * No authentication required.
270
+ * Batch tag lookup via POST (public).
271
+ *
272
+ * `POST /public/collection/:collectionId/tags/lookup`
248
273
  *
249
- * @param collectionId - Identifier of the collection to search within
250
- * @param data - Request containing array of tagIds and optional embed parameter
251
- * @returns Promise resolving to PublicBatchLookupResponse with deduplicated related data
252
- * @throws ErrorResponse if the request fails
274
+ * Tags not belonging to this collection are filtered out silently.
275
+ * Returns deduplicated embedded objects alongside the tag array.
276
+ *
277
+ * @param collectionId - Collection context
278
+ * @param data - `{ tagIds: string[], embed?: string }`
279
+ * @returns `{ count: number, tags: Tag[], embedded: TagEmbedded }`
253
280
  *
254
281
  * @example
255
282
  * ```typescript
256
- * const result = await tags.publicBatchLookup('coll_123', {
257
- * tagIds: ['TAG001', 'TAG002', 'TAG003'],
258
- * embed: 'collection,product'
283
+ * const { count, tags: list, embedded } = await tags.lookupTags('coll_123', {
284
+ * tagIds: ['NFC-001', 'NFC-002', 'NFC-003'],
285
+ * embed: 'product,proof',
259
286
  * })
260
- *
261
- * // Access tags and deduplicated collections/products
262
- * console.log(result.tags['TAG001'])
263
- * console.log(result.collections)
264
- * console.log(result.products)
265
287
  * ```
266
288
  */
267
- async function lookupTags(data) {
268
- const path = `/public/tags/lookup`;
289
+ async function lookupTags(collectionId, data) {
290
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/tags/lookup`;
269
291
  return post(path, data);
270
292
  }
271
293
  tags.lookupTags = lookupTags;
272
294
  /**
273
- * Backward-compat: Public batch lookup with collectionId parameter (ignored).
274
- * Calls global route under /public/tags/lookup.
295
+ * Batch tag lookup via GET (public).
296
+ *
297
+ * `GET /public/collection/:collectionId/tags/lookup?tagIds=NFC-001,NFC-002&embed=product`
298
+ *
299
+ * @param collectionId - Collection context
300
+ * @param params - `tagIds` is comma-separated; `embed` is optional
301
+ * @returns `{ count: number, tags: Tag[], embedded: TagEmbedded }`
275
302
  */
276
- async function publicBatchLookup(_collectionId, data) {
277
- return lookupTags(data);
303
+ async function lookupTagsQuery(collectionId, params) {
304
+ const q = new URLSearchParams();
305
+ q.append('tagIds', params.tagIds);
306
+ if (params.embed)
307
+ q.append('embed', params.embed);
308
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/tags/lookup?${q.toString()}`;
309
+ return request(path);
278
310
  }
279
- tags.publicBatchLookup = publicBatchLookup;
311
+ tags.lookupTagsQuery = lookupTagsQuery;
280
312
  /**
281
- * Public batch lookup of multiple tags using query parameters (GET).
282
- * Only returns tags from the specified collection.
283
- * Alternative to publicBatchLookup for simple GET requests.
284
- * No authentication required.
313
+ * Reverse lookup by ref via GET (public).
314
+ *
315
+ * `GET /public/collection/:collectionId/tags/by-ref?refType=container&refId=<uuid>&embed=ref`
285
316
  *
286
- * @param collectionId - Identifier of the collection to search within
287
- * @param params - Query parameters with comma-separated tagIds and optional embed
288
- * @returns Promise resolving to PublicBatchLookupQueryResponse
289
- * @throws ErrorResponse if the request fails
317
+ * @param collectionId - Collection context
318
+ * @param params - `refType` and `refId` are required; `embed` is optional
319
+ * @returns `{ tags: Tag[], embedded: TagEmbedded }`
290
320
  *
291
321
  * @example
292
322
  * ```typescript
293
- * const result = await tags.publicBatchLookupQuery('coll_123', {
294
- * tagIds: 'TAG001,TAG002,TAG003',
295
- * embed: 'collection'
323
+ * const { tags: linked, embedded } = await tags.publicByRef('coll_123', {
324
+ * refType: 'container',
325
+ * refId: 'container-uuid',
326
+ * embed: 'container',
296
327
  * })
328
+ * const container = embedded.containers?.[containerId]
297
329
  * ```
298
330
  */
299
- async function lookupTagsQuery(params) {
300
- const queryParams = new URLSearchParams();
301
- queryParams.append('tagIds', params.tagIds);
331
+ async function publicByRef(collectionId, params) {
332
+ const q = new URLSearchParams();
333
+ q.append('refType', params.refType);
334
+ q.append('refId', params.refId);
302
335
  if (params.embed)
303
- queryParams.append('embed', params.embed);
304
- const path = `/public/tags/lookup?${queryParams.toString()}`;
336
+ q.append('embed', params.embed);
337
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/tags/by-ref?${q.toString()}`;
305
338
  return request(path);
306
339
  }
307
- tags.lookupTagsQuery = lookupTagsQuery;
340
+ tags.publicByRef = publicByRef;
308
341
  /**
309
- * Backward-compat: Public batch lookup (GET) with collectionId parameter (ignored).
310
- * Calls global route under /public/tags/lookup.
342
+ * Reverse lookup by ref via POST (public).
343
+ *
344
+ * `POST /public/collection/:collectionId/tags/by-ref`
345
+ *
346
+ * @param collectionId - Collection context
347
+ * @param data - `{ refType, refId, embed? }`
348
+ * @returns `{ tags: Tag[], embedded: TagEmbedded }`
311
349
  */
312
- async function publicBatchLookupQuery(_collectionId, params) {
313
- return lookupTagsQuery(params);
350
+ async function publicByRefPost(collectionId, data) {
351
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/tags/by-ref`;
352
+ return post(path, data);
314
353
  }
315
- tags.publicBatchLookupQuery = publicBatchLookupQuery;
354
+ tags.publicByRefPost = publicByRefPost;
316
355
  })(tags || (tags = {}));