@proveanything/smartlinks 1.15.22 → 1.15.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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.15.22 | Generated: 2026-08-27T17:34:12.788Z
3
+ Version: 1.15.23 | Generated: 2026-08-28T10:57:24.158Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -8275,6 +8275,32 @@ interface PortalPathParams {
8275
8275
  }
8276
8276
  ```
8277
8277
 
8278
+ **Gs1DigitalLinkParams** (interface)
8279
+ ```typescript
8280
+ interface Gs1DigitalLinkParams {
8281
+ collection?: Collection | { shortId?: string; portalUrl?: string; redirectUrl?: string }
8282
+ domain?: string
8283
+ gtin?: string
8284
+ product?: Product
8285
+ ownGtin?: boolean
8286
+ cpv?: string | { id: string }
8287
+ variant?: string | { id: string }
8288
+ lot?: string | { id: string }
8289
+ batch?: BatchResponse | string
8290
+ serial?: string | { id?: string; serialNumber?: string }
8291
+ expiry?: string | Date
8292
+ * Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
8293
+ * `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
8294
+ * `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
8295
+ * the path in canonical order; everything else becomes a query-string data attribute.
8296
+ ais?: Record<string, string | number | Date>
8297
+ linkType?: string
8298
+ queryParams?: Record<string, string>
8299
+ customDomain?: boolean
8300
+ pathOnly?: boolean
8301
+ }
8302
+ ```
8303
+
8278
8304
  ## API Functions
8279
8305
 
8280
8306
  ### analytics.admin
@@ -219,6 +219,52 @@ utils.buildPortalPath({
219
219
  // Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
220
220
  ```
221
221
 
222
+ ## GS1 Digital Links — `buildGs1DigitalLink(params)`
223
+
224
+ `buildPortalPath` covers portal routing; when you specifically want a **GS1 Digital Link**
225
+ with the full range of Application Identifiers (AIs), use `buildGs1DigitalLink`. It emits
226
+ path qualifiers in the **canonical GS1 order** — `/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}` —
227
+ routes data attributes (expiry, production date, weights, …) to the query string, and applies
228
+ the same custom-domain scoping (bare `/01/` on a custom domain or for a master GTIN, `/gc/{shortId}`
229
+ otherwise).
230
+
231
+ **Named inputs** for the common AIs, plus a generic **`ais` map** for anything else:
232
+
233
+ | Input | AI | Notes |
234
+ |-------|----|-------|
235
+ | `gtin` / `product.gtin` | 01 | Primary identifier (required) |
236
+ | `cpv` / `variant` | 22 | Consumer Product Variant (path) |
237
+ | `lot` / `batch` | 10 | Batch/lot (path); a batch object also supplies its expiry |
238
+ | `serial` | 21 | Specific item — a string, or an object (`serialNumber` ?? `id`, e.g. a proof / serial / virtual id) |
239
+ | `expiry` | 17 | Date → `YYMMDD` (query) |
240
+ | `ais: { [ai]: value }` | any | Date AIs (11/12/13/15/16/17) formatted `YYMMDD`; qualifier AIs (22/10/21) go in the path; the rest become query attributes |
241
+ | `linkType` | — | GS1 `linkType`, added as `?linkType=` |
242
+
243
+ ```typescript
244
+ // Variant + lot + serial + expiry, on a custom domain
245
+ utils.buildGs1DigitalLink({
246
+ collection, // portalUrl = https://acme.com
247
+ gtin: '05012345678900',
248
+ variant: 'red',
249
+ lot: 'LOT42',
250
+ serial: proof, // AI 21 from proof.serialNumber ?? proof.id
251
+ expiry: '2026-06-30',
252
+ })
253
+ // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
254
+
255
+ // Arbitrary AIs via the generic map
256
+ utils.buildGs1DigitalLink({
257
+ collection,
258
+ gtin: '05012345678900',
259
+ ais: { '11': new Date(2025, 0, 1), '3103': '000500' }, // production date + net weight
260
+ linkType: 'gs1:pip',
261
+ })
262
+ // → https://.../01/05012345678900?11=250101&3103=000500&linkType=gs1%3Apip
263
+ ```
264
+
265
+ `buildPortalPath` delegates its GTIN paths to this function, so both stay in sync (and both now
266
+ emit qualifiers in canonical `22 → 10 → 21` order).
267
+
222
268
  ## Use Cases
223
269
 
224
270
  ### QR Code Generation
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { iframe } from "./iframe";
5
5
  export * as cache from './cache';
6
6
  export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
7
7
  export * as utils from './utils';
8
- export type { PortalPathParams, ConditionParams, ConditionDebugOptions, ConditionDebugLogger, ConditionSet, Condition, UserInfo, ProductInfo, ProofInfo, CollectionInfo, } from './utils';
8
+ export type { PortalPathParams, Gs1DigitalLinkParams, ConditionParams, ConditionDebugOptions, ConditionDebugLogger, ConditionSet, Condition, UserInfo, ProductInfo, ProofInfo, CollectionInfo, } from './utils';
9
9
  export type { LoginResponse, VerifyTokenResponse, AccountInfoResponse, AuthLocation, } from "./api/auth";
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";
@@ -101,3 +101,87 @@ export interface PortalPathParams {
101
101
  * ```
102
102
  */
103
103
  export declare function buildPortalPath(params: PortalPathParams): string;
104
+ export interface Gs1DigitalLinkParams {
105
+ /** Collection — provides the base domain (`portalUrl`), `shortId` (for `/gc` scoping) and custom-domain detection. */
106
+ collection?: Collection | {
107
+ shortId?: string;
108
+ portalUrl?: string;
109
+ redirectUrl?: string;
110
+ };
111
+ /** Explicit base domain (e.g. `"https://acme.com"`), overriding `collection.portalUrl`. */
112
+ domain?: string;
113
+ /** GTIN — the product's AI 01 identifier. Required (or provide `product`). */
114
+ gtin?: string;
115
+ /** Product object — extracts `gtin` and `ownGtin` when not given explicitly. */
116
+ product?: Product;
117
+ /** Override the global-owner flag; otherwise read from `product.ownGtin`. */
118
+ ownGtin?: boolean;
119
+ /** Consumer Product Variant (AI 22). A string or an object with `id`. */
120
+ cpv?: string | {
121
+ id: string;
122
+ };
123
+ /** Alias of `cpv` (AI 22). */
124
+ variant?: string | {
125
+ id: string;
126
+ };
127
+ /** Batch / lot (AI 10). A string or an object with `id`. */
128
+ lot?: string | {
129
+ id: string;
130
+ };
131
+ /** Alias of `lot` (AI 10). A `BatchResponse` also contributes its expiry date (AI 17) unless `expiry` is set. */
132
+ batch?: BatchResponse | string;
133
+ /** Serial (AI 21) — the specific item / proof. A string (serial / NFC / virtual id) or an object (`serialNumber` ?? `id`, e.g. a proof). */
134
+ serial?: string | {
135
+ id?: string;
136
+ serialNumber?: string;
137
+ };
138
+ /** Expiry date (AI 17). A `Date`, ISO string, or `YYMMDD` string. */
139
+ expiry?: string | Date;
140
+ /**
141
+ * Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
142
+ * `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
143
+ * `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
144
+ * the path in canonical order; everything else becomes a query-string data attribute.
145
+ */
146
+ ais?: Record<string, string | number | Date>;
147
+ /** GS1 `linkType` (added as a `?linkType=` query param). */
148
+ linkType?: string;
149
+ /** Additional non-GS1 query params. */
150
+ queryParams?: Record<string, string>;
151
+ /** Override custom-domain detection (see {@link buildPortalPath}). */
152
+ customDomain?: boolean;
153
+ /** Return only the path, without a domain. */
154
+ pathOnly?: boolean;
155
+ }
156
+ /**
157
+ * Builds a GS1 Digital Link for a product, with full support for the standard
158
+ * Application Identifiers. Path qualifiers are emitted in the canonical GS1 order
159
+ * (`/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}`); data attributes (expiry, production
160
+ * date, weights, …) become query params; `linkType` is appended for GS1 resolution.
161
+ *
162
+ * A bare `/01/{gtin}` is used when the product owns the GTIN globally (`ownGtin`) or
163
+ * the collection is on its own custom domain; otherwise the GTIN is scoped to the
164
+ * collection with the `/gc/{shortId}` prefix on the shared platform domain.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * // GTIN + variant + lot + serial + expiry, on a custom domain
169
+ * buildGs1DigitalLink({
170
+ * collection, // portalUrl = https://acme.com
171
+ * gtin: '05012345678900',
172
+ * variant: 'red',
173
+ * lot: 'LOT42',
174
+ * serial: proof, // AI 21 from proof.serialNumber ?? proof.id
175
+ * expiry: '2026-06-30',
176
+ * })
177
+ * // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
178
+ *
179
+ * // Arbitrary AIs via the generic map
180
+ * buildGs1DigitalLink({
181
+ * collection, gtin: '05012345678900',
182
+ * ais: { '11': new Date('2025-01-01'), '3103': '000500' }, // production date + net weight (kg)
183
+ * linkType: 'gs1:pip',
184
+ * })
185
+ * ```
186
+ */
187
+ export declare function buildGs1DigitalLink(params: Gs1DigitalLinkParams): string;
@@ -135,35 +135,25 @@ export function buildPortalPath(params) {
135
135
  ? proof
136
136
  : proof.id
137
137
  : undefined;
138
- let pathname = '';
139
- const searchParams = new URLSearchParams();
140
- // Build pathname based on GTIN or product ID
138
+ // GTIN delegate to the GS1 Digital Link generator (canonical AI ordering:
139
+ // /01/{gtin}/22/{cpv}/10/{lot}/21/{serial}, data attributes as query params).
141
140
  if (gtin) {
142
- // GS1 Digital Link format. A bare `/01/{gtin}` is used when the product owns the
143
- // GTIN globally (master registry) OR when we're on the collection's custom domain
144
- // (the host resolves the collection). Otherwise the GTIN must be scoped to the
145
- // collection with the `/gc/{shortId}` prefix on the shared platform domain.
146
- if (ownGtin || customDomain) {
147
- pathname = `/01/${gtin}`;
148
- }
149
- else {
150
- pathname = `/gc/${shortId}/01/${gtin}`;
151
- }
152
- // Add batch (GS1 AI 10)
153
- if (extractedBatchId) {
154
- pathname += `/10/${extractedBatchId}`;
155
- // Add expiry date as query param (GS1 AI 17)
156
- if (expiryDate) {
157
- const dateStr = formatExpiryDate(expiryDate);
158
- searchParams.append('17', dateStr);
159
- }
160
- }
161
- // Add variant (GS1 AI 22)
162
- if (variantId) {
163
- pathname += `/22/${variantId}`;
164
- }
141
+ return buildGs1DigitalLink({
142
+ domain: baseUrl,
143
+ collection,
144
+ gtin,
145
+ ownGtin,
146
+ customDomain,
147
+ variant: variantId,
148
+ lot: extractedBatchId,
149
+ expiry: expiryDate,
150
+ queryParams,
151
+ pathOnly,
152
+ });
165
153
  }
166
- else if (extractedProductId) {
154
+ let pathname = '';
155
+ const searchParams = new URLSearchParams();
156
+ if (extractedProductId) {
167
157
  // Regular product path
168
158
  pathname = `/c/${shortId}/${extractedProductId}`;
169
159
  // Add proof to path
@@ -205,3 +195,129 @@ function formatExpiryDate(date) {
205
195
  const day = date.getDate().toString().padStart(2, '0');
206
196
  return `${year}${month}${day}`;
207
197
  }
198
+ // ─── GS1 Digital Link generator ──────────────────────────────────────────────
199
+ /** GS1 path qualifiers for a GTIN (AI 01) key, in the canonical GS1 Digital Link order. */
200
+ const GTIN_PATH_QUALIFIERS = ['22', '10', '21'];
201
+ /** GS1 Application Identifiers whose value is a date — formatted `YYMMDD`. */
202
+ const DATE_AIS = new Set(['11', '12', '13', '15', '16', '17']);
203
+ /** Pull a code out of a string or an object (`serialNumber` preferred, then `id`). */
204
+ function extractCode(v) {
205
+ var _a;
206
+ if (v == null)
207
+ return undefined;
208
+ if (typeof v === 'string')
209
+ return v;
210
+ return (_a = v.serialNumber) !== null && _a !== void 0 ? _a : v.id;
211
+ }
212
+ /**
213
+ * Builds a GS1 Digital Link for a product, with full support for the standard
214
+ * Application Identifiers. Path qualifiers are emitted in the canonical GS1 order
215
+ * (`/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}`); data attributes (expiry, production
216
+ * date, weights, …) become query params; `linkType` is appended for GS1 resolution.
217
+ *
218
+ * A bare `/01/{gtin}` is used when the product owns the GTIN globally (`ownGtin`) or
219
+ * the collection is on its own custom domain; otherwise the GTIN is scoped to the
220
+ * collection with the `/gc/{shortId}` prefix on the shared platform domain.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * // GTIN + variant + lot + serial + expiry, on a custom domain
225
+ * buildGs1DigitalLink({
226
+ * collection, // portalUrl = https://acme.com
227
+ * gtin: '05012345678900',
228
+ * variant: 'red',
229
+ * lot: 'LOT42',
230
+ * serial: proof, // AI 21 from proof.serialNumber ?? proof.id
231
+ * expiry: '2026-06-30',
232
+ * })
233
+ * // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
234
+ *
235
+ * // Arbitrary AIs via the generic map
236
+ * buildGs1DigitalLink({
237
+ * collection, gtin: '05012345678900',
238
+ * ais: { '11': new Date('2025-01-01'), '3103': '000500' }, // production date + net weight (kg)
239
+ * linkType: 'gs1:pip',
240
+ * })
241
+ * ```
242
+ */
243
+ export function buildGs1DigitalLink(params) {
244
+ var _a, _b, _c, _d, _e, _f;
245
+ const { collection, product, expiry, linkType, ais = {}, queryParams = {}, pathOnly = false } = params;
246
+ const gtin = (_b = (_a = params.gtin) !== null && _a !== void 0 ? _a : product === null || product === void 0 ? void 0 : product.gtin) !== null && _b !== void 0 ? _b : undefined;
247
+ if (!gtin) {
248
+ throw new Error('buildGs1DigitalLink requires a `gtin` (or a `product` that has one)');
249
+ }
250
+ const ownGtin = (_c = params.ownGtin) !== null && _c !== void 0 ? _c : (product && 'ownGtin' in product ? !!product.ownGtin : false);
251
+ // Base domain + custom-domain scoping (same rules as buildPortalPath).
252
+ const shortId = collection === null || collection === void 0 ? void 0 : collection.shortId;
253
+ const baseUrl = (_d = params.domain) !== null && _d !== void 0 ? _d : (collection && 'portalUrl' in collection ? collection.portalUrl : undefined);
254
+ const redirectUrl = collection && 'redirectUrl' in collection ? collection.redirectUrl : undefined;
255
+ const customDomain = (_e = params.customDomain) !== null && _e !== void 0 ? _e : (!!redirectUrl || baseIsCustomDomain(baseUrl));
256
+ // ── Path qualifiers (AI 22 → 10 → 21) ──
257
+ const pathAIs = {};
258
+ const cpv = extractCode((_f = params.cpv) !== null && _f !== void 0 ? _f : params.variant);
259
+ if (cpv != null)
260
+ pathAIs['22'] = cpv;
261
+ let batchExpiry;
262
+ let lot = extractCode(params.lot);
263
+ if (lot == null && params.batch != null) {
264
+ if (typeof params.batch === 'string') {
265
+ lot = params.batch;
266
+ }
267
+ else {
268
+ lot = params.batch.id;
269
+ const exp = params.batch.expiryDate;
270
+ if (exp) {
271
+ batchExpiry = (typeof exp === 'object' && 'seconds' in exp)
272
+ ? new Date(exp.seconds * 1000)
273
+ : exp;
274
+ }
275
+ }
276
+ }
277
+ if (lot != null)
278
+ pathAIs['10'] = lot;
279
+ const serial = extractCode(params.serial);
280
+ if (serial != null)
281
+ pathAIs['21'] = serial;
282
+ // ── Data attributes (query string) ──
283
+ const attrs = {};
284
+ const setAttr = (ai, value) => {
285
+ attrs[ai] = DATE_AIS.has(ai) && (value instanceof Date || typeof value === 'string')
286
+ ? formatExpiryDate(value)
287
+ : String(value);
288
+ };
289
+ const effectiveExpiry = expiry !== null && expiry !== void 0 ? expiry : batchExpiry;
290
+ if (effectiveExpiry != null)
291
+ setAttr('17', effectiveExpiry);
292
+ // Generic AI map: classify each entry as a path qualifier or a data attribute.
293
+ for (const [ai, value] of Object.entries(ais)) {
294
+ if (value == null)
295
+ continue;
296
+ if (GTIN_PATH_QUALIFIERS.includes(ai)) {
297
+ pathAIs[ai] = String(value);
298
+ }
299
+ else {
300
+ setAttr(ai, value);
301
+ }
302
+ }
303
+ // ── Compose the path ──
304
+ let pathname = (ownGtin || customDomain || !shortId) ? `/01/${gtin}` : `/gc/${shortId}/01/${gtin}`;
305
+ for (const ai of GTIN_PATH_QUALIFIERS) {
306
+ if (pathAIs[ai] != null)
307
+ pathname += `/${ai}/${pathAIs[ai]}`;
308
+ }
309
+ // ── Query string ──
310
+ const searchParams = new URLSearchParams();
311
+ for (const [ai, value] of Object.entries(attrs))
312
+ searchParams.append(ai, value);
313
+ if (linkType)
314
+ searchParams.append('linkType', linkType);
315
+ for (const [k, v] of Object.entries(queryParams))
316
+ searchParams.append(k, v);
317
+ const queryString = searchParams.toString();
318
+ const fullPath = pathname + (queryString ? `?${queryString}` : '');
319
+ if (pathOnly)
320
+ return fullPath;
321
+ const domain = baseUrl || 'https://smartlinks.app';
322
+ return domain.replace(/\/$/, '') + fullPath;
323
+ }
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.15.22 | Generated: 2026-08-27T17:34:12.788Z
3
+ Version: 1.15.23 | Generated: 2026-08-28T10:57:24.158Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -8275,6 +8275,32 @@ interface PortalPathParams {
8275
8275
  }
8276
8276
  ```
8277
8277
 
8278
+ **Gs1DigitalLinkParams** (interface)
8279
+ ```typescript
8280
+ interface Gs1DigitalLinkParams {
8281
+ collection?: Collection | { shortId?: string; portalUrl?: string; redirectUrl?: string }
8282
+ domain?: string
8283
+ gtin?: string
8284
+ product?: Product
8285
+ ownGtin?: boolean
8286
+ cpv?: string | { id: string }
8287
+ variant?: string | { id: string }
8288
+ lot?: string | { id: string }
8289
+ batch?: BatchResponse | string
8290
+ serial?: string | { id?: string; serialNumber?: string }
8291
+ expiry?: string | Date
8292
+ * Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
8293
+ * `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
8294
+ * `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
8295
+ * the path in canonical order; everything else becomes a query-string data attribute.
8296
+ ais?: Record<string, string | number | Date>
8297
+ linkType?: string
8298
+ queryParams?: Record<string, string>
8299
+ customDomain?: boolean
8300
+ pathOnly?: boolean
8301
+ }
8302
+ ```
8303
+
8278
8304
  ## API Functions
8279
8305
 
8280
8306
  ### analytics.admin
package/docs/utils.md CHANGED
@@ -219,6 +219,52 @@ utils.buildPortalPath({
219
219
  // Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
220
220
  ```
221
221
 
222
+ ## GS1 Digital Links — `buildGs1DigitalLink(params)`
223
+
224
+ `buildPortalPath` covers portal routing; when you specifically want a **GS1 Digital Link**
225
+ with the full range of Application Identifiers (AIs), use `buildGs1DigitalLink`. It emits
226
+ path qualifiers in the **canonical GS1 order** — `/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}` —
227
+ routes data attributes (expiry, production date, weights, …) to the query string, and applies
228
+ the same custom-domain scoping (bare `/01/` on a custom domain or for a master GTIN, `/gc/{shortId}`
229
+ otherwise).
230
+
231
+ **Named inputs** for the common AIs, plus a generic **`ais` map** for anything else:
232
+
233
+ | Input | AI | Notes |
234
+ |-------|----|-------|
235
+ | `gtin` / `product.gtin` | 01 | Primary identifier (required) |
236
+ | `cpv` / `variant` | 22 | Consumer Product Variant (path) |
237
+ | `lot` / `batch` | 10 | Batch/lot (path); a batch object also supplies its expiry |
238
+ | `serial` | 21 | Specific item — a string, or an object (`serialNumber` ?? `id`, e.g. a proof / serial / virtual id) |
239
+ | `expiry` | 17 | Date → `YYMMDD` (query) |
240
+ | `ais: { [ai]: value }` | any | Date AIs (11/12/13/15/16/17) formatted `YYMMDD`; qualifier AIs (22/10/21) go in the path; the rest become query attributes |
241
+ | `linkType` | — | GS1 `linkType`, added as `?linkType=` |
242
+
243
+ ```typescript
244
+ // Variant + lot + serial + expiry, on a custom domain
245
+ utils.buildGs1DigitalLink({
246
+ collection, // portalUrl = https://acme.com
247
+ gtin: '05012345678900',
248
+ variant: 'red',
249
+ lot: 'LOT42',
250
+ serial: proof, // AI 21 from proof.serialNumber ?? proof.id
251
+ expiry: '2026-06-30',
252
+ })
253
+ // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
254
+
255
+ // Arbitrary AIs via the generic map
256
+ utils.buildGs1DigitalLink({
257
+ collection,
258
+ gtin: '05012345678900',
259
+ ais: { '11': new Date(2025, 0, 1), '3103': '000500' }, // production date + net weight
260
+ linkType: 'gs1:pip',
261
+ })
262
+ // → https://.../01/05012345678900?11=250101&3103=000500&linkType=gs1%3Apip
263
+ ```
264
+
265
+ `buildPortalPath` delegates its GTIN paths to this function, so both stay in sync (and both now
266
+ emit qualifiers in canonical `22 → 10 → 21` order).
267
+
222
268
  ## Use Cases
223
269
 
224
270
  ### QR Code Generation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.15.22",
3
+ "version": "1.15.23",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",