@proveanything/smartlinks 1.6.6 → 1.7.0

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.
@@ -0,0 +1,316 @@
1
+ // src/api/containers.ts
2
+ import { request, post, patch, del, requestWithOptions } from "../http";
3
+ // ─── Internal helper ──────────────────────────────────────────────────────────
4
+ function buildContainerQuery(params) {
5
+ const q = new URLSearchParams();
6
+ for (const [key, value] of Object.entries(params)) {
7
+ if (value !== undefined && value !== null) {
8
+ q.append(key, String(value));
9
+ }
10
+ }
11
+ const qs = q.toString();
12
+ return qs ? `?${qs}` : '';
13
+ }
14
+ // ─── Namespace ────────────────────────────────────────────────────────────────
15
+ /**
16
+ * Container Tracking API.
17
+ *
18
+ * Containers are physical or logical groupings (pallets, fridges, casks,
19
+ * shipping containers, warehouses, etc.) that support **hierarchical nesting**
20
+ * via `parentContainerId`. Each container can hold items of type `tag`,
21
+ * `proof`, `serial`, `order_item`, or even other `container`s.
22
+ *
23
+ * ### Admin vs Public
24
+ * - **Admin** routes (`/admin/collection/:id/containers`) allow full CRUD and
25
+ * mutation of item membership.
26
+ * - **Public** routes (`/public/collection/:id/containers`) are read-only.
27
+ * Soft-deleted containers and containers with `metadata.publicListing === false`
28
+ * are excluded from list results.
29
+ *
30
+ * Attestations against containers are managed through the `attestations`
31
+ * namespace. Container-scoped public shortcuts are available via
32
+ * `attestations.publicContainer*` helpers.
33
+ *
34
+ * @see docs/container-tracking.md
35
+ */
36
+ export var containers;
37
+ (function (containers) {
38
+ // ==========================================================================
39
+ // Admin — containers
40
+ // ==========================================================================
41
+ /**
42
+ * Create a new container (admin).
43
+ *
44
+ * @param collectionId - Collection context
45
+ * @param data - Container definition; `containerType` is required
46
+ * @returns The created `Container` record
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const cask = await containers.create('coll_123', {
51
+ * containerType: 'cask',
52
+ * ref: 'CASK-0042',
53
+ * name: 'Cask 42 — Single Malt',
54
+ * metadata: { distilleryYear: 2019, capacity: 200 },
55
+ * })
56
+ * ```
57
+ */
58
+ async function create(collectionId, data) {
59
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers`;
60
+ return post(path, data);
61
+ }
62
+ containers.create = create;
63
+ /**
64
+ * List containers (admin).
65
+ *
66
+ * Supports filtering by type, status, ref, parent, and top-level flag.
67
+ *
68
+ * @param collectionId - Collection context
69
+ * @param params - Optional filter and pagination parameters
70
+ * @returns `{ containers: Container[], limit: number, offset: number }`
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // All active pallets
75
+ * const { containers: pallets } = await containers.list('coll_123', {
76
+ * containerType: 'pallet',
77
+ * status: 'active',
78
+ * limit: 50,
79
+ * })
80
+ *
81
+ * // Top-level containers only
82
+ * const { containers: roots } = await containers.list('coll_123', { topLevel: true })
83
+ * ```
84
+ */
85
+ async function list(collectionId, params) {
86
+ const qs = buildContainerQuery(params !== null && params !== void 0 ? params : {});
87
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers${qs}`;
88
+ return request(path);
89
+ }
90
+ containers.list = list;
91
+ /**
92
+ * Reverse lookup — find all containers currently holding a specific item (admin).
93
+ *
94
+ * @param collectionId - Collection context
95
+ * @param params - `itemType` and `itemId` are required
96
+ * @returns `{ containers: Container[] }`
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const { containers: holding } = await containers.findForItem('coll_123', {
101
+ * itemType: 'proof',
102
+ * itemId: 'proof-uuid',
103
+ * })
104
+ * ```
105
+ */
106
+ async function findForItem(collectionId, params) {
107
+ const qs = buildContainerQuery(params);
108
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/find-for-item${qs}`;
109
+ return request(path);
110
+ }
111
+ containers.findForItem = findForItem;
112
+ /**
113
+ * Get a single container by ID (admin).
114
+ *
115
+ * Pass `?tree=true` to recursively embed children, and/or
116
+ * `?includeContents=true` to embed the current item list.
117
+ *
118
+ * @param collectionId - Collection context
119
+ * @param containerId - Container UUID
120
+ * @param params - Optional display options
121
+ * @returns `Container` (with optional `children` and `items` arrays)
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * // Flat
126
+ * const cask = await containers.get('coll_123', 'cask-uuid')
127
+ *
128
+ * // Full tree with contents
129
+ * const tree = await containers.get('coll_123', 'warehouse-uuid', {
130
+ * tree: true,
131
+ * treeDepth: 3,
132
+ * includeContents: true,
133
+ * })
134
+ * ```
135
+ */
136
+ async function get(collectionId, containerId, params) {
137
+ const qs = buildContainerQuery(params !== null && params !== void 0 ? params : {});
138
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}${qs}`;
139
+ return request(path);
140
+ }
141
+ containers.get = get;
142
+ /**
143
+ * Partially update a container (admin).
144
+ *
145
+ * Only fields present in the request body are modified.
146
+ * Pass `parentContainerId: null` to promote a container to top-level.
147
+ *
148
+ * @param collectionId - Collection context
149
+ * @param containerId - Container UUID
150
+ * @param data - Fields to update
151
+ * @returns Updated `Container`
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const updated = await containers.update('coll_123', 'cask-uuid', {
156
+ * status: 'archived',
157
+ * metadata: { bottledAt: '2025-04-01' },
158
+ * })
159
+ * ```
160
+ */
161
+ async function update(collectionId, containerId, data) {
162
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}`;
163
+ return patch(path, data);
164
+ }
165
+ containers.update = update;
166
+ /**
167
+ * Soft-delete a container (admin).
168
+ *
169
+ * Sets `deletedAt`; the record and its full item history remain queryable
170
+ * by admins. Public API responses automatically exclude deleted containers.
171
+ *
172
+ * @param collectionId - Collection context
173
+ * @param containerId - Container UUID
174
+ * @returns `{ success: true }`
175
+ */
176
+ async function remove(collectionId, containerId) {
177
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}`;
178
+ return del(path);
179
+ }
180
+ containers.remove = remove;
181
+ // ==========================================================================
182
+ // Admin — item membership
183
+ // ==========================================================================
184
+ /**
185
+ * List items currently (or historically) inside a container (admin).
186
+ *
187
+ * Pass `history: true` to include removed items and see the full membership log.
188
+ *
189
+ * @param collectionId - Collection context
190
+ * @param containerId - Container UUID
191
+ * @param params - Optional filters and pagination
192
+ * @returns `{ items: ContainerItem[], limit: number, offset: number }`
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * // Current contents
197
+ * const { items } = await containers.listItems('coll_123', 'cask-uuid')
198
+ *
199
+ * // Full history including removed items
200
+ * const { items: history } = await containers.listItems('coll_123', 'cask-uuid', { history: true })
201
+ * ```
202
+ */
203
+ async function listItems(collectionId, containerId, params) {
204
+ const qs = buildContainerQuery(params !== null && params !== void 0 ? params : {});
205
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}/items${qs}`;
206
+ return request(path);
207
+ }
208
+ containers.listItems = listItems;
209
+ /**
210
+ * Add one or more items to a container (admin).
211
+ *
212
+ * Each item requires `itemType` and `itemId`. Pass `productId` / `proofId`
213
+ * for denormalisation convenience.
214
+ *
215
+ * @param collectionId - Collection context
216
+ * @param containerId - Container UUID
217
+ * @param data - Items to add
218
+ * @returns `{ items: ContainerItem[] }` — the newly created membership records
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const { items } = await containers.addItems('coll_123', 'pallet-uuid', {
223
+ * items: [
224
+ * { itemType: 'tag', itemId: 'NFC-00AABBCC' },
225
+ * { itemType: 'proof', itemId: 'proof-uuid', productId: 'product-id' },
226
+ * ],
227
+ * })
228
+ * ```
229
+ */
230
+ async function addItems(collectionId, containerId, data) {
231
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}/items`;
232
+ return post(path, data);
233
+ }
234
+ containers.addItems = addItems;
235
+ /**
236
+ * Soft-remove items from a container (admin).
237
+ *
238
+ * Sets `removedAt` on the specified `ContainerItem` records. The records
239
+ * are retained in the history log and can be queried with `history: true`.
240
+ *
241
+ * @param collectionId - Collection context
242
+ * @param containerId - Container UUID
243
+ * @param data - `ids` array of `ContainerItem` UUIDs to remove
244
+ * @returns `{ success: true, removedCount: number }`
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * const result = await containers.removeItems('coll_123', 'pallet-uuid', {
249
+ * ids: ['container-item-uuid-1', 'container-item-uuid-2'],
250
+ * })
251
+ * console.log(`Removed ${result.removedCount} items`)
252
+ * ```
253
+ */
254
+ async function removeItems(collectionId, containerId, data) {
255
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}/items`;
256
+ return requestWithOptions(path, {
257
+ method: 'DELETE',
258
+ headers: { 'Content-Type': 'application/json' },
259
+ body: JSON.stringify(data),
260
+ });
261
+ }
262
+ containers.removeItems = removeItems;
263
+ // ==========================================================================
264
+ // Public — read-only
265
+ // ==========================================================================
266
+ /**
267
+ * List containers (public).
268
+ *
269
+ * Soft-deleted containers and containers with `metadata.publicListing === false`
270
+ * are excluded from results.
271
+ *
272
+ * @param collectionId - Collection context
273
+ * @param params - Optional filter and pagination parameters
274
+ * @returns `{ containers: Container[] }`
275
+ */
276
+ async function publicList(collectionId, params) {
277
+ const qs = buildContainerQuery(params !== null && params !== void 0 ? params : {});
278
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/containers${qs}`;
279
+ return request(path);
280
+ }
281
+ containers.publicList = publicList;
282
+ /**
283
+ * Get a single container (public).
284
+ *
285
+ * Soft-deleted containers return a 404. Same `?tree` and
286
+ * `?includeContents` options as the admin version.
287
+ *
288
+ * @param collectionId - Collection context
289
+ * @param containerId - Container UUID
290
+ * @param params - Optional display options
291
+ * @returns `Container`
292
+ */
293
+ async function publicGet(collectionId, containerId, params) {
294
+ const qs = buildContainerQuery(params !== null && params !== void 0 ? params : {});
295
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}${qs}`;
296
+ return request(path);
297
+ }
298
+ containers.publicGet = publicGet;
299
+ /**
300
+ * List current contents of a container (public).
301
+ *
302
+ * Returns only items where `removedAt` is null. No `?history` option on
303
+ * the public side.
304
+ *
305
+ * @param collectionId - Collection context
306
+ * @param containerId - Container UUID
307
+ * @param params - Optional pagination
308
+ * @returns `{ items: ContainerItem[], limit: number, offset: number }`
309
+ */
310
+ async function publicListItems(collectionId, containerId, params) {
311
+ const qs = buildContainerQuery(params !== null && params !== void 0 ? params : {});
312
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/containers/${encodeURIComponent(containerId)}/items${qs}`;
313
+ return request(path);
314
+ }
315
+ containers.publicListItems = publicListItems;
316
+ })(containers || (containers = {}));
@@ -29,3 +29,5 @@ export * as realtime from "./realtime";
29
29
  export { tags } from "./tags";
30
30
  export { order } from "./order";
31
31
  export { app } from "./appObjects";
32
+ export { attestations } from "./attestations";
33
+ export { containers } from "./containers";
package/dist/api/index.js CHANGED
@@ -32,3 +32,5 @@ export { realtime_1 as realtime };
32
32
  export { tags } from "./tags";
33
33
  export { order } from "./order";
34
34
  export { app } from "./appObjects";
35
+ export { attestations } from "./attestations";
36
+ export { containers } from "./containers";
@@ -1,4 +1,4 @@
1
- import { CreateTagRequest, CreateTagResponse, CreateTagsBatchRequest, CreateTagsBatchResponse, UpdateTagRequest, UpdateTagResponse, DeleteTagResponse, GetTagResponse, ListTagsRequest, ListTagsResponse, PublicGetTagRequest, PublicGetTagResponse, PublicBatchLookupRequest, PublicBatchLookupResponse, PublicBatchLookupQueryRequest, PublicBatchLookupQueryResponse } from "../types/tags";
1
+ import { CreateTagRequest, CreateTagResponse, CreateTagsBatchRequest, CreateTagsBatchResponse, UpdateTagRequest, UpdateTagResponse, DeleteTagResponse, GetTagResponse, ListTagsRequest, ListTagsResponse, PublicGetTagRequest, PublicGetTagResponse, PublicBatchLookupRequest, PublicBatchLookupResponse, PublicBatchLookupQueryRequest, PublicBatchLookupQueryResponse, ReverseTagLookupParams, ReverseTagLookupResponse } from "../types/tags";
2
2
  /**
3
3
  * Tag Management API
4
4
  *
@@ -127,6 +127,25 @@ export declare namespace tags {
127
127
  * ```
128
128
  */
129
129
  function list(collectionId: string, params?: ListTagsRequest): Promise<ListTagsResponse>;
130
+ /**
131
+ * Reverse lookup — find all tags linked to a given app object (admin).
132
+ *
133
+ * Uses a global cross-shard index keyed on `(orgId, refType, refId)`, so it
134
+ * is safe to call without knowing which collection the object belongs to.
135
+ *
136
+ * @param collectionId - Collection context (used for auth scope)
137
+ * @param params - `refType` and `refId` are required
138
+ * @returns `{ tags: Tag[] }`
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const { tags: linked } = await tags.byRef('coll_123', {
143
+ * refType: 'container',
144
+ * refId: 'container-uuid',
145
+ * })
146
+ * ```
147
+ */
148
+ function byRef(collectionId: string, params: ReverseTagLookupParams): Promise<ReverseTagLookupResponse>;
130
149
  /**
131
150
  * Public lookup of a single tag by tagId (global).
132
151
  * Optionally embed related collection, product, or proof data.
package/dist/api/tags.js CHANGED
@@ -163,11 +163,41 @@ export var tags;
163
163
  queryParams.append('variantId', params.variantId);
164
164
  if (params === null || params === void 0 ? void 0 : params.batchId)
165
165
  queryParams.append('batchId', params.batchId);
166
+ if (params === null || params === void 0 ? void 0 : params.refType)
167
+ queryParams.append('refType', params.refType);
168
+ if (params === null || params === void 0 ? void 0 : params.refId)
169
+ queryParams.append('refId', params.refId);
166
170
  const query = queryParams.toString();
167
171
  const path = `/admin/collection/${encodeURIComponent(collectionId)}/tags${query ? `?${query}` : ''}`;
168
172
  return request(path);
169
173
  }
170
174
  tags.list = list;
175
+ /**
176
+ * Reverse lookup — find all tags linked to a given app object (admin).
177
+ *
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
+ *
181
+ * @param collectionId - Collection context (used for auth scope)
182
+ * @param params - `refType` and `refId` are required
183
+ * @returns `{ tags: Tag[] }`
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const { tags: linked } = await tags.byRef('coll_123', {
188
+ * refType: 'container',
189
+ * refId: 'container-uuid',
190
+ * })
191
+ * ```
192
+ */
193
+ 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()}`;
198
+ return request(path);
199
+ }
200
+ tags.byRef = byRef;
171
201
  // ============================================================================
172
202
  // Public Endpoints
173
203
  // ============================================================================