@ai-matrx/print 0.1.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.
package/dist/qr.cjs ADDED
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/qr.ts
31
+ var qr_exports = {};
32
+ __export(qr_exports, {
33
+ DEFAULT_QR_EC_LEVEL: () => DEFAULT_QR_EC_LEVEL,
34
+ QR_MAX_VERSION: () => QR_MAX_VERSION,
35
+ QR_MIN_QUIET_ZONE_MODULES: () => QR_MIN_QUIET_ZONE_MODULES,
36
+ QR_MIN_VERSION: () => QR_MIN_VERSION,
37
+ buildGs1DigitalLink: () => buildGs1DigitalLink,
38
+ buildShortResolverUrl: () => buildShortResolverUrl,
39
+ generateQrDataUrl: () => generateQrDataUrl,
40
+ generateQrSvg: () => generateQrSvg,
41
+ minQrVersion: () => minQrVersion,
42
+ qrByteCapacity: () => qrByteCapacity,
43
+ qrModuleCount: () => qrModuleCount
44
+ });
45
+ module.exports = __toCommonJS(qr_exports);
46
+ var QR_MIN_QUIET_ZONE_MODULES = 4;
47
+ var DEFAULT_QR_EC_LEVEL = "M";
48
+ var QR_MIN_VERSION = 1;
49
+ var QR_MAX_VERSION = 40;
50
+ var BYTE_CAPACITY = [
51
+ /* v1 */
52
+ [17, 14, 11, 7],
53
+ /* v2 */
54
+ [32, 26, 20, 14],
55
+ /* v3 */
56
+ [53, 42, 32, 24],
57
+ /* v4 */
58
+ [78, 62, 46, 34],
59
+ /* v5 */
60
+ [106, 84, 60, 44],
61
+ /* v6 */
62
+ [134, 106, 74, 58],
63
+ /* v7 */
64
+ [154, 122, 86, 64],
65
+ /* v8 */
66
+ [192, 152, 108, 84],
67
+ /* v9 */
68
+ [230, 180, 130, 98],
69
+ /* v10 */
70
+ [271, 213, 151, 119],
71
+ /* v11 */
72
+ [321, 251, 177, 137],
73
+ /* v12 */
74
+ [367, 287, 203, 155],
75
+ /* v13 */
76
+ [425, 331, 241, 177],
77
+ /* v14 */
78
+ [458, 362, 258, 194],
79
+ /* v15 */
80
+ [520, 412, 292, 220],
81
+ /* v16 */
82
+ [586, 450, 322, 250],
83
+ /* v17 */
84
+ [644, 504, 364, 280],
85
+ /* v18 */
86
+ [718, 560, 394, 310],
87
+ /* v19 */
88
+ [792, 624, 442, 338],
89
+ /* v20 */
90
+ [858, 666, 482, 382],
91
+ /* v21 */
92
+ [929, 711, 509, 403],
93
+ /* v22 */
94
+ [1003, 779, 565, 439],
95
+ /* v23 */
96
+ [1091, 857, 611, 461],
97
+ /* v24 */
98
+ [1171, 911, 661, 511],
99
+ /* v25 */
100
+ [1273, 997, 715, 535],
101
+ /* v26 */
102
+ [1367, 1059, 751, 593],
103
+ /* v27 */
104
+ [1465, 1125, 805, 625],
105
+ /* v28 */
106
+ [1528, 1190, 868, 658],
107
+ /* v29 */
108
+ [1628, 1264, 908, 698],
109
+ /* v30 */
110
+ [1732, 1370, 982, 742],
111
+ /* v31 */
112
+ [1840, 1452, 1030, 790],
113
+ /* v32 */
114
+ [1952, 1538, 1112, 842],
115
+ /* v33 */
116
+ [2068, 1628, 1168, 898],
117
+ /* v34 */
118
+ [2188, 1722, 1228, 958],
119
+ /* v35 */
120
+ [2303, 1809, 1283, 983],
121
+ /* v36 */
122
+ [2431, 1911, 1351, 1051],
123
+ /* v37 */
124
+ [2563, 1989, 1423, 1093],
125
+ /* v38 */
126
+ [2699, 2099, 1499, 1139],
127
+ /* v39 */
128
+ [2809, 2213, 1579, 1219],
129
+ /* v40 */
130
+ [2953, 2331, 1663, 1273]
131
+ ];
132
+ var EC_COLUMN = { L: 0, M: 1, Q: 2, H: 3 };
133
+ function assertEcLevel(ecLevel) {
134
+ const column = EC_COLUMN[ecLevel];
135
+ if (column === void 0) {
136
+ throw new Error(`Unknown QR error correction level "${String(ecLevel)}". Expected one of "L", "M", "Q", "H".`);
137
+ }
138
+ return column;
139
+ }
140
+ function assertVersion(version) {
141
+ if (typeof version !== "number" || !Number.isInteger(version) || version < QR_MIN_VERSION || version > QR_MAX_VERSION) {
142
+ throw new Error(
143
+ `QR version must be an integer between ${QR_MIN_VERSION} and ${QR_MAX_VERSION}, got ${String(version)}.`
144
+ );
145
+ }
146
+ return version;
147
+ }
148
+ function qrByteCapacity(version, ecLevel) {
149
+ const column = assertEcLevel(ecLevel);
150
+ const row = BYTE_CAPACITY[assertVersion(version) - 1];
151
+ if (row === void 0) {
152
+ throw new Error(`No QR capacity row for version ${version}.`);
153
+ }
154
+ return row[column];
155
+ }
156
+ function qrModuleCount(version) {
157
+ return 17 + 4 * assertVersion(version);
158
+ }
159
+ function minQrVersion(payloadByteLen, ecLevel) {
160
+ const column = assertEcLevel(ecLevel);
161
+ if (typeof payloadByteLen !== "number" || !Number.isInteger(payloadByteLen) || payloadByteLen < 0) {
162
+ throw new Error(`QR payload byte length must be a non-negative integer, got ${String(payloadByteLen)}.`);
163
+ }
164
+ for (let version = QR_MIN_VERSION; version <= QR_MAX_VERSION; version += 1) {
165
+ const row = BYTE_CAPACITY[version - 1];
166
+ if (row !== void 0 && row[column] >= payloadByteLen) {
167
+ return version;
168
+ }
169
+ }
170
+ const max = qrByteCapacity(QR_MAX_VERSION, ecLevel);
171
+ throw new Error(
172
+ `QR payload of ${payloadByteLen} bytes does not fit any QR version at error correction level "${ecLevel}" (version ${QR_MAX_VERSION} holds ${max} bytes). Shorten the payload \u2014 a short resolver URL is the intended fix \u2014 or, only for a large clean code, drop to a lower error correction level.`
173
+ );
174
+ }
175
+ function resolveEcLevel(ecLevel) {
176
+ if (ecLevel === void 0) return DEFAULT_QR_EC_LEVEL;
177
+ assertEcLevel(ecLevel);
178
+ return ecLevel;
179
+ }
180
+ function resolveQuietZone(quietZoneModules) {
181
+ if (typeof quietZoneModules !== "number" || !Number.isFinite(quietZoneModules)) {
182
+ return QR_MIN_QUIET_ZONE_MODULES;
183
+ }
184
+ return Math.max(QR_MIN_QUIET_ZONE_MODULES, Math.floor(quietZoneModules));
185
+ }
186
+ function resolveWidthPx(widthPx) {
187
+ if (widthPx === void 0) return void 0;
188
+ if (typeof widthPx !== "number" || !Number.isFinite(widthPx) || widthPx <= 0) {
189
+ throw new Error(`QR widthPx must be a positive number, got ${String(widthPx)}.`);
190
+ }
191
+ return Math.round(widthPx);
192
+ }
193
+ function assertValue(value) {
194
+ if (typeof value !== "string" || value.length === 0) {
195
+ throw new Error("QR value must be a non-empty string.");
196
+ }
197
+ return value;
198
+ }
199
+ async function generateQrDataUrl(value, opts) {
200
+ assertValue(value);
201
+ const width = resolveWidthPx(opts?.widthPx);
202
+ const options = {
203
+ errorCorrectionLevel: resolveEcLevel(opts?.ecLevel),
204
+ margin: resolveQuietZone(opts?.quietZoneModules)
205
+ };
206
+ if (width !== void 0) options.width = width;
207
+ const QRCode = await import("qrcode");
208
+ return QRCode.toDataURL(value, options);
209
+ }
210
+ async function generateQrSvg(value, opts) {
211
+ assertValue(value);
212
+ const width = resolveWidthPx(opts?.widthPx);
213
+ const options = {
214
+ type: "svg",
215
+ errorCorrectionLevel: resolveEcLevel(opts?.ecLevel),
216
+ margin: resolveQuietZone(opts?.quietZoneModules)
217
+ };
218
+ if (width !== void 0) options.width = width;
219
+ const QRCode = await import("qrcode");
220
+ return QRCode.toString(value, options);
221
+ }
222
+ function normalizeOrigin(origin) {
223
+ if (typeof origin !== "string" || origin.trim() === "") {
224
+ throw new Error('QR origin is required \u2014 pass something like "https://example.com".');
225
+ }
226
+ const trimmed = origin.trim();
227
+ let parsed;
228
+ try {
229
+ parsed = new URL(trimmed);
230
+ } catch {
231
+ throw new Error(`QR origin "${origin}" is not a valid absolute URL. Expected something like "https://example.com".`);
232
+ }
233
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
234
+ throw new Error(`QR origin "${origin}" must use http or https, got "${parsed.protocol}".`);
235
+ }
236
+ if (parsed.search !== "" || parsed.hash !== "") {
237
+ throw new Error(`QR origin "${origin}" must not carry a query string or fragment.`);
238
+ }
239
+ return trimmed.replace(/\/+$/, "");
240
+ }
241
+ function buildShortResolverUrl(opts) {
242
+ const origin = normalizeOrigin(opts.origin);
243
+ const code = opts.code;
244
+ if (typeof code !== "string" || code.trim() === "") {
245
+ throw new Error("QR resolver code is required \u2014 it is minted server-side and passed in here as an opaque value.");
246
+ }
247
+ const rawPath = opts.path === void 0 ? "/l" : opts.path;
248
+ if (typeof rawPath !== "string") {
249
+ throw new Error(`QR resolver path must be a string, got ${String(rawPath)}.`);
250
+ }
251
+ if (rawPath.includes("?") || rawPath.includes("#")) {
252
+ throw new Error(`QR resolver path "${rawPath}" must not contain a query string or fragment.`);
253
+ }
254
+ const cleanedPath = rawPath.trim().replace(/^\/+/, "").replace(/\/+$/, "");
255
+ const prefix = cleanedPath === "" ? "" : `/${cleanedPath}`;
256
+ return `${origin}${prefix}/${encodeURIComponent(code)}`;
257
+ }
258
+ function gs1CheckDigit(payloadDigits) {
259
+ let sum = 0;
260
+ let weight = 3;
261
+ for (let i = payloadDigits.length - 1; i >= 0; i -= 1) {
262
+ sum += (payloadDigits.charCodeAt(i) - 48) * weight;
263
+ weight = weight === 3 ? 1 : 3;
264
+ }
265
+ return (10 - sum % 10) % 10;
266
+ }
267
+ function normalizeGtin(gtin) {
268
+ if (typeof gtin !== "string" || gtin.trim() === "") {
269
+ throw new Error("GTIN is required for a GS1 Digital Link.");
270
+ }
271
+ const raw = gtin.trim();
272
+ if (!/^[0-9]+$/.test(raw)) {
273
+ throw new Error(`GTIN "${gtin}" must contain digits only.`);
274
+ }
275
+ if (raw.length !== 8 && raw.length !== 12 && raw.length !== 13 && raw.length !== 14) {
276
+ throw new Error(`GTIN "${gtin}" must be 8, 12, 13, or 14 digits, got ${raw.length}.`);
277
+ }
278
+ const expected = gs1CheckDigit(raw.slice(0, -1));
279
+ const actual = raw.charCodeAt(raw.length - 1) - 48;
280
+ if (expected !== actual) {
281
+ throw new Error(`GTIN "${raw}" has an invalid GS1 check digit: expected ${expected}, got ${actual}.`);
282
+ }
283
+ return raw.padStart(14, "0");
284
+ }
285
+ function daysInMonth(year, month) {
286
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
287
+ }
288
+ function normalizeExpiry(expiry) {
289
+ if (typeof expiry !== "string" || !/^[0-9]{6}$/.test(expiry.trim())) {
290
+ throw new Error(`GS1 expiry (AI 17) must be exactly 6 digits in YYMMDD form, got "${String(expiry)}".`);
291
+ }
292
+ const raw = expiry.trim();
293
+ const year = 2e3 + Number(raw.slice(0, 2));
294
+ const month = Number(raw.slice(2, 4));
295
+ const day = Number(raw.slice(4, 6));
296
+ if (month < 1 || month > 12) {
297
+ throw new Error(`GS1 expiry (AI 17) "${raw}" has an invalid month "${raw.slice(2, 4)}" \u2014 expected 01-12.`);
298
+ }
299
+ const max = daysInMonth(year, month);
300
+ if (day > max) {
301
+ throw new Error(
302
+ `GS1 expiry (AI 17) "${raw}" has an invalid day "${raw.slice(4, 6)}" \u2014 ${year}-${raw.slice(2, 4)} has ${max} days (00 is legal and means end of month).`
303
+ );
304
+ }
305
+ return raw;
306
+ }
307
+ function assertQualifier(value, ai, name) {
308
+ if (typeof value !== "string" || value.trim() === "") {
309
+ throw new Error(`GS1 ${name} (AI ${ai}) must be a non-empty string when provided.`);
310
+ }
311
+ return value;
312
+ }
313
+ function buildGs1DigitalLink(opts) {
314
+ const origin = normalizeOrigin(opts.origin);
315
+ const gtin14 = normalizeGtin(opts.gtin);
316
+ let url = `${origin}/01/${gtin14}`;
317
+ if (opts.lot !== void 0) {
318
+ url += `/10/${encodeURIComponent(assertQualifier(opts.lot, "10", "lot"))}`;
319
+ }
320
+ if (opts.serial !== void 0) {
321
+ url += `/21/${encodeURIComponent(assertQualifier(opts.serial, "21", "serial"))}`;
322
+ }
323
+ if (opts.expiry !== void 0) {
324
+ url += `?17=${normalizeExpiry(opts.expiry)}`;
325
+ }
326
+ return url;
327
+ }
328
+ //# sourceMappingURL=qr.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/qr.ts"],"sourcesContent":["/**\n * @ai-matrx/print/qr — QR GENERATION.\n *\n * New code (no port source). Decoding is NOT here: it lives in\n * `@ai-matrx/kit/qr`. This module only ever turns a value into a symbol, and\n * builds the two payload shapes the platform actually prints:\n *\n * 1. a SHORT RESOLVER URL (`https://origin/l/<code>`) — the default\n * e-commerce payload. The code value is an OPAQUE INPUT: minting,\n * uniqueness, collision handling and lookup are all server-side concerns.\n * Nothing in this file generates, randomizes, or validates the identity of\n * a code beyond \"it is a non-empty string we will percent-encode\".\n * 2. a GS1 DIGITAL LINK (`https://origin/01/<gtin14>[/10/<lot>][/21/<serial>][?17=YYMMDD]`)\n * for supply-chain interoperability — the thing a retailer's scanner\n * understands without knowing anything about AI Matrx.\n *\n * Two hard defaults that are NOT style choices:\n * - Error correction defaults to \"M\", never \"L\". These end up on warehouse\n * labels that get scuffed, wrapped around curved stock and read at an\n * angle; \"L\" is only ever right for a huge, clean, flat code.\n * - The quiet zone is CLAMPED to >= 4 modules (ISO/IEC 18004 §6.3.8 minimum)\n * no matter what the caller passes. A tight quiet zone is the single most\n * common cause of a label that scans on the design monitor and fails at the\n * dock door.\n *\n * The module is import-inert: `qrcode` is only ever pulled in via\n * `await import(\"qrcode\")` inside a function body, so importing this file from\n * a React Server Component (or any tree-shaken bundle that never draws a QR)\n * costs nothing. No module-level mutable state, no React, no DOM at import\n * time.\n */\n\nimport type { QRCodeToDataURLOptions, QRCodeToStringOptions } from \"qrcode\";\n\n// ─── Contract ────────────────────────────────────────────────────────────────\n\nexport type QrEcLevel = \"L\" | \"M\" | \"Q\" | \"H\";\n\nexport interface QrOptions {\n /** Error correction. Default \"M\" — NEVER \"L\" (warehouse labels get scuffed; L is only right for huge clean codes). */\n ecLevel?: QrEcLevel;\n /** Quiet zone in modules. Default 4. CLAMPED to >= 4 at runtime — ISO 18004 spec minimum, not a style choice. */\n quietZoneModules?: number;\n /** Rendered width in pixels (dataURL/canvas lanes). */\n widthPx?: number;\n}\n\n/** ISO/IEC 18004 §6.3.8 minimum quiet zone. Callers cannot go below this. */\nexport const QR_MIN_QUIET_ZONE_MODULES = 4;\n\n/** The default error correction level for every Matrx-printed code. */\nexport const DEFAULT_QR_EC_LEVEL: QrEcLevel = \"M\";\n\n/** Lowest and highest QR symbol versions (ISO/IEC 18004). */\nexport const QR_MIN_VERSION = 1;\nexport const QR_MAX_VERSION = 40;\n\n// ─── Capacity table ──────────────────────────────────────────────────────────\n\n/**\n * ISO/IEC 18004 BYTE-mode data capacity, versions 1–40 × [L, M, Q, H],\n * in bytes (8-bit characters).\n *\n * This is the full spec table, not an approximation. It is cross-checked in\n * `qr.test.ts` against the `qrcode` reference implementation for a spread of\n * versions at all four levels — every entry that test samples must both fit at\n * exactly `capacity` bytes and fail at `capacity + 1`.\n *\n * Anchors worth eyeballing: v1 = L17/M14/Q11/H7 · v2 M=26 · v3 M=42 ·\n * v4 M=62 · v10 M=213 · v40 = L2953/M2331/Q1663/H1273.\n */\nconst BYTE_CAPACITY: readonly (readonly [number, number, number, number])[] = [\n /* v1 */ [17, 14, 11, 7],\n /* v2 */ [32, 26, 20, 14],\n /* v3 */ [53, 42, 32, 24],\n /* v4 */ [78, 62, 46, 34],\n /* v5 */ [106, 84, 60, 44],\n /* v6 */ [134, 106, 74, 58],\n /* v7 */ [154, 122, 86, 64],\n /* v8 */ [192, 152, 108, 84],\n /* v9 */ [230, 180, 130, 98],\n /* v10 */ [271, 213, 151, 119],\n /* v11 */ [321, 251, 177, 137],\n /* v12 */ [367, 287, 203, 155],\n /* v13 */ [425, 331, 241, 177],\n /* v14 */ [458, 362, 258, 194],\n /* v15 */ [520, 412, 292, 220],\n /* v16 */ [586, 450, 322, 250],\n /* v17 */ [644, 504, 364, 280],\n /* v18 */ [718, 560, 394, 310],\n /* v19 */ [792, 624, 442, 338],\n /* v20 */ [858, 666, 482, 382],\n /* v21 */ [929, 711, 509, 403],\n /* v22 */ [1003, 779, 565, 439],\n /* v23 */ [1091, 857, 611, 461],\n /* v24 */ [1171, 911, 661, 511],\n /* v25 */ [1273, 997, 715, 535],\n /* v26 */ [1367, 1059, 751, 593],\n /* v27 */ [1465, 1125, 805, 625],\n /* v28 */ [1528, 1190, 868, 658],\n /* v29 */ [1628, 1264, 908, 698],\n /* v30 */ [1732, 1370, 982, 742],\n /* v31 */ [1840, 1452, 1030, 790],\n /* v32 */ [1952, 1538, 1112, 842],\n /* v33 */ [2068, 1628, 1168, 898],\n /* v34 */ [2188, 1722, 1228, 958],\n /* v35 */ [2303, 1809, 1283, 983],\n /* v36 */ [2431, 1911, 1351, 1051],\n /* v37 */ [2563, 1989, 1423, 1093],\n /* v38 */ [2699, 2099, 1499, 1139],\n /* v39 */ [2809, 2213, 1579, 1219],\n /* v40 */ [2953, 2331, 1663, 1273],\n];\n\nconst EC_COLUMN: Readonly<Record<QrEcLevel, 0 | 1 | 2 | 3>> = { L: 0, M: 1, Q: 2, H: 3 };\n\nfunction assertEcLevel(ecLevel: QrEcLevel): 0 | 1 | 2 | 3 {\n const column = EC_COLUMN[ecLevel];\n if (column === undefined) {\n throw new Error(`Unknown QR error correction level \"${String(ecLevel)}\". Expected one of \"L\", \"M\", \"Q\", \"H\".`);\n }\n return column;\n}\n\nfunction assertVersion(version: number): number {\n if (typeof version !== \"number\" || !Number.isInteger(version) || version < QR_MIN_VERSION || version > QR_MAX_VERSION) {\n throw new Error(\n `QR version must be an integer between ${QR_MIN_VERSION} and ${QR_MAX_VERSION}, got ${String(version)}.`,\n );\n }\n return version;\n}\n\n/**\n * BYTE-mode capacity in bytes for a given version/level.\n * Throws on an out-of-range version or unknown level.\n */\nexport function qrByteCapacity(version: number, ecLevel: QrEcLevel): number {\n const column = assertEcLevel(ecLevel);\n const row = BYTE_CAPACITY[assertVersion(version) - 1];\n if (row === undefined) {\n throw new Error(`No QR capacity row for version ${version}.`);\n }\n return row[column];\n}\n\n/** Modules per side for a QR version: 17 + 4*version. */\nexport function qrModuleCount(version: number): number {\n return 17 + 4 * assertVersion(version);\n}\n\n/**\n * Smallest QR version (1-40) whose BYTE-mode capacity at `ecLevel` fits\n * `payloadByteLen`. Throws a clear Error when nothing fits.\n *\n * NOTE: this is the *byte* length, not the character length — callers holding\n * a JS string should measure it with `new TextEncoder().encode(s).length`, not\n * `s.length`, or every non-ASCII character will silently under-count.\n */\nexport function minQrVersion(payloadByteLen: number, ecLevel: QrEcLevel): number {\n const column = assertEcLevel(ecLevel);\n if (typeof payloadByteLen !== \"number\" || !Number.isInteger(payloadByteLen) || payloadByteLen < 0) {\n throw new Error(`QR payload byte length must be a non-negative integer, got ${String(payloadByteLen)}.`);\n }\n for (let version = QR_MIN_VERSION; version <= QR_MAX_VERSION; version += 1) {\n const row = BYTE_CAPACITY[version - 1];\n if (row !== undefined && row[column] >= payloadByteLen) {\n return version;\n }\n }\n const max = qrByteCapacity(QR_MAX_VERSION, ecLevel);\n throw new Error(\n `QR payload of ${payloadByteLen} bytes does not fit any QR version at error correction level \"${ecLevel}\" ` +\n `(version ${QR_MAX_VERSION} holds ${max} bytes). Shorten the payload — a short resolver URL is the intended ` +\n `fix — or, only for a large clean code, drop to a lower error correction level.`,\n );\n}\n\n// ─── Rendering ───────────────────────────────────────────────────────────────\n\nfunction resolveEcLevel(ecLevel: QrEcLevel | undefined): QrEcLevel {\n if (ecLevel === undefined) return DEFAULT_QR_EC_LEVEL;\n assertEcLevel(ecLevel);\n return ecLevel;\n}\n\n/**\n * `margin` in the `qrcode` library IS the quiet zone measured in modules.\n * Anything below the ISO minimum is silently raised — a caller asking for 0 is\n * asking for a label that will not scan, and we would rather print a slightly\n * smaller symbol than an unreadable one.\n */\nfunction resolveQuietZone(quietZoneModules: number | undefined): number {\n if (typeof quietZoneModules !== \"number\" || !Number.isFinite(quietZoneModules)) {\n return QR_MIN_QUIET_ZONE_MODULES;\n }\n return Math.max(QR_MIN_QUIET_ZONE_MODULES, Math.floor(quietZoneModules));\n}\n\nfunction resolveWidthPx(widthPx: number | undefined): number | undefined {\n if (widthPx === undefined) return undefined;\n if (typeof widthPx !== \"number\" || !Number.isFinite(widthPx) || widthPx <= 0) {\n throw new Error(`QR widthPx must be a positive number, got ${String(widthPx)}.`);\n }\n return Math.round(widthPx);\n}\n\nfunction assertValue(value: string): string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(\"QR value must be a non-empty string.\");\n }\n return value;\n}\n\n/** PNG data URL. Lazy-imports \"qrcode\" at call time (import-inert module). */\nexport async function generateQrDataUrl(value: string, opts?: QrOptions): Promise<string> {\n assertValue(value);\n const width = resolveWidthPx(opts?.widthPx);\n const options: QRCodeToDataURLOptions = {\n errorCorrectionLevel: resolveEcLevel(opts?.ecLevel),\n margin: resolveQuietZone(opts?.quietZoneModules),\n };\n // exactOptionalPropertyTypes: omit the key entirely rather than assigning undefined.\n if (width !== undefined) options.width = width;\n\n const QRCode = await import(\"qrcode\");\n return QRCode.toDataURL(value, options);\n}\n\n/** SVG markup string. Same lazy import. */\nexport async function generateQrSvg(value: string, opts?: QrOptions): Promise<string> {\n assertValue(value);\n const width = resolveWidthPx(opts?.widthPx);\n const options: QRCodeToStringOptions = {\n type: \"svg\",\n errorCorrectionLevel: resolveEcLevel(opts?.ecLevel),\n margin: resolveQuietZone(opts?.quietZoneModules),\n };\n if (width !== undefined) options.width = width;\n\n const QRCode = await import(\"qrcode\");\n return QRCode.toString(value, options);\n}\n\n// ─── Payload builders ────────────────────────────────────────────────────────\n\n/**\n * Validate + normalize an origin for a payload builder.\n * Accepts `https://x.com`, `https://x.com/`, `https://x.com/go` — returns it\n * with every trailing slash stripped so callers can always concatenate a\n * leading-slash path without producing `//`.\n */\nfunction normalizeOrigin(origin: string): string {\n if (typeof origin !== \"string\" || origin.trim() === \"\") {\n throw new Error('QR origin is required — pass something like \"https://example.com\".');\n }\n const trimmed = origin.trim();\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new Error(`QR origin \"${origin}\" is not a valid absolute URL. Expected something like \"https://example.com\".`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(`QR origin \"${origin}\" must use http or https, got \"${parsed.protocol}\".`);\n }\n if (parsed.search !== \"\" || parsed.hash !== \"\") {\n throw new Error(`QR origin \"${origin}\" must not carry a query string or fragment.`);\n }\n return trimmed.replace(/\\/+$/, \"\");\n}\n\n/**\n * Short resolver URL — the platform's default e-commerce payload.\n *\n * The CODE VALUE IS AN OPAQUE INPUT: minting and uniqueness are server-side,\n * never generated here. This function's whole job is to put a code the server\n * already owns behind a URL short enough to stay in a low QR version.\n *\n * Result looks like `https://x.com/l/AB12cd`.\n */\nexport function buildShortResolverUrl(opts: { origin: string; code: string; path?: string }): string {\n const origin = normalizeOrigin(opts.origin);\n\n const code = opts.code;\n if (typeof code !== \"string\" || code.trim() === \"\") {\n throw new Error(\"QR resolver code is required — it is minted server-side and passed in here as an opaque value.\");\n }\n\n const rawPath = opts.path === undefined ? \"/l\" : opts.path;\n if (typeof rawPath !== \"string\") {\n throw new Error(`QR resolver path must be a string, got ${String(rawPath)}.`);\n }\n if (rawPath.includes(\"?\") || rawPath.includes(\"#\")) {\n throw new Error(`QR resolver path \"${rawPath}\" must not contain a query string or fragment.`);\n }\n const cleanedPath = rawPath.trim().replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const prefix = cleanedPath === \"\" ? \"\" : `/${cleanedPath}`;\n\n return `${origin}${prefix}/${encodeURIComponent(code)}`;\n}\n\n/**\n * GS1 mod-10 check digit over the payload (i.e. the digits WITHOUT the check\n * digit): weights 3 and 1 alternating from the right. Length-independent, so a\n * GTIN-12 and its zero-padded GTIN-14 form produce the same digit — which is\n * exactly why we validate BEFORE padding.\n */\nfunction gs1CheckDigit(payloadDigits: string): number {\n let sum = 0;\n let weight = 3;\n for (let i = payloadDigits.length - 1; i >= 0; i -= 1) {\n sum += (payloadDigits.charCodeAt(i) - 48) * weight;\n weight = weight === 3 ? 1 : 3;\n }\n return (10 - (sum % 10)) % 10;\n}\n\n/** Validate a GTIN-8/12/13/14 (own check digit) and return it zero-padded to GTIN-14. */\nfunction normalizeGtin(gtin: string): string {\n if (typeof gtin !== \"string\" || gtin.trim() === \"\") {\n throw new Error(\"GTIN is required for a GS1 Digital Link.\");\n }\n const raw = gtin.trim();\n if (!/^[0-9]+$/.test(raw)) {\n throw new Error(`GTIN \"${gtin}\" must contain digits only.`);\n }\n if (raw.length !== 8 && raw.length !== 12 && raw.length !== 13 && raw.length !== 14) {\n throw new Error(`GTIN \"${gtin}\" must be 8, 12, 13, or 14 digits, got ${raw.length}.`);\n }\n const expected = gs1CheckDigit(raw.slice(0, -1));\n const actual = raw.charCodeAt(raw.length - 1) - 48;\n if (expected !== actual) {\n throw new Error(`GTIN \"${raw}\" has an invalid GS1 check digit: expected ${expected}, got ${actual}.`);\n }\n return raw.padStart(14, \"0\");\n}\n\nfunction daysInMonth(year: number, month: number): number {\n return new Date(Date.UTC(year, month, 0)).getUTCDate();\n}\n\n/**\n * AI 17 expiration date — STRICT \"YYMMDD\", 6 digits, nothing else.\n * `DD = 00` is honoured: GS1 defines it as \"last day of the month\".\n */\nfunction normalizeExpiry(expiry: string): string {\n if (typeof expiry !== \"string\" || !/^[0-9]{6}$/.test(expiry.trim())) {\n throw new Error(`GS1 expiry (AI 17) must be exactly 6 digits in YYMMDD form, got \"${String(expiry)}\".`);\n }\n const raw = expiry.trim();\n const year = 2000 + Number(raw.slice(0, 2));\n const month = Number(raw.slice(2, 4));\n const day = Number(raw.slice(4, 6));\n if (month < 1 || month > 12) {\n throw new Error(`GS1 expiry (AI 17) \"${raw}\" has an invalid month \"${raw.slice(2, 4)}\" — expected 01-12.`);\n }\n const max = daysInMonth(year, month);\n // DD=00 is the GS1 \"end of month\" convention and stays legal.\n if (day > max) {\n throw new Error(\n `GS1 expiry (AI 17) \"${raw}\" has an invalid day \"${raw.slice(4, 6)}\" — ${year}-${raw.slice(2, 4)} has ${max} days ` +\n `(00 is legal and means end of month).`,\n );\n }\n return raw;\n}\n\nfunction assertQualifier(value: string, ai: string, name: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new Error(`GS1 ${name} (AI ${ai}) must be a non-empty string when provided.`);\n }\n return value;\n}\n\n/** GS1 Digital Link builder for supply-chain interoperability. */\nexport function buildGs1DigitalLink(opts: {\n origin: string;\n gtin: string;\n lot?: string;\n serial?: string;\n expiry?: string;\n}): string {\n const origin = normalizeOrigin(opts.origin);\n const gtin14 = normalizeGtin(opts.gtin);\n\n // Path order is fixed by the GS1 Digital Link grammar:\n // /01/{gtin14} then the optional qualifiers /10/{lot} then /21/{serial}.\n let url = `${origin}/01/${gtin14}`;\n if (opts.lot !== undefined) {\n url += `/10/${encodeURIComponent(assertQualifier(opts.lot, \"10\", \"lot\"))}`;\n }\n if (opts.serial !== undefined) {\n url += `/21/${encodeURIComponent(assertQualifier(opts.serial, \"21\", \"serial\"))}`;\n }\n // AI 17 is a DATA ATTRIBUTE, not a path qualifier — it belongs in the query\n // string, never as a /17/ path segment.\n if (opts.expiry !== undefined) {\n url += `?17=${normalizeExpiry(opts.expiry)}`;\n }\n return url;\n}\n"],"mappings":"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","names":[]}
package/dist/qr.d.cts ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @ai-matrx/print/qr — QR GENERATION.
3
+ *
4
+ * New code (no port source). Decoding is NOT here: it lives in
5
+ * `@ai-matrx/kit/qr`. This module only ever turns a value into a symbol, and
6
+ * builds the two payload shapes the platform actually prints:
7
+ *
8
+ * 1. a SHORT RESOLVER URL (`https://origin/l/<code>`) — the default
9
+ * e-commerce payload. The code value is an OPAQUE INPUT: minting,
10
+ * uniqueness, collision handling and lookup are all server-side concerns.
11
+ * Nothing in this file generates, randomizes, or validates the identity of
12
+ * a code beyond "it is a non-empty string we will percent-encode".
13
+ * 2. a GS1 DIGITAL LINK (`https://origin/01/<gtin14>[/10/<lot>][/21/<serial>][?17=YYMMDD]`)
14
+ * for supply-chain interoperability — the thing a retailer's scanner
15
+ * understands without knowing anything about AI Matrx.
16
+ *
17
+ * Two hard defaults that are NOT style choices:
18
+ * - Error correction defaults to "M", never "L". These end up on warehouse
19
+ * labels that get scuffed, wrapped around curved stock and read at an
20
+ * angle; "L" is only ever right for a huge, clean, flat code.
21
+ * - The quiet zone is CLAMPED to >= 4 modules (ISO/IEC 18004 §6.3.8 minimum)
22
+ * no matter what the caller passes. A tight quiet zone is the single most
23
+ * common cause of a label that scans on the design monitor and fails at the
24
+ * dock door.
25
+ *
26
+ * The module is import-inert: `qrcode` is only ever pulled in via
27
+ * `await import("qrcode")` inside a function body, so importing this file from
28
+ * a React Server Component (or any tree-shaken bundle that never draws a QR)
29
+ * costs nothing. No module-level mutable state, no React, no DOM at import
30
+ * time.
31
+ */
32
+ type QrEcLevel = "L" | "M" | "Q" | "H";
33
+ interface QrOptions {
34
+ /** Error correction. Default "M" — NEVER "L" (warehouse labels get scuffed; L is only right for huge clean codes). */
35
+ ecLevel?: QrEcLevel;
36
+ /** Quiet zone in modules. Default 4. CLAMPED to >= 4 at runtime — ISO 18004 spec minimum, not a style choice. */
37
+ quietZoneModules?: number;
38
+ /** Rendered width in pixels (dataURL/canvas lanes). */
39
+ widthPx?: number;
40
+ }
41
+ /** ISO/IEC 18004 §6.3.8 minimum quiet zone. Callers cannot go below this. */
42
+ declare const QR_MIN_QUIET_ZONE_MODULES = 4;
43
+ /** The default error correction level for every Matrx-printed code. */
44
+ declare const DEFAULT_QR_EC_LEVEL: QrEcLevel;
45
+ /** Lowest and highest QR symbol versions (ISO/IEC 18004). */
46
+ declare const QR_MIN_VERSION = 1;
47
+ declare const QR_MAX_VERSION = 40;
48
+ /**
49
+ * BYTE-mode capacity in bytes for a given version/level.
50
+ * Throws on an out-of-range version or unknown level.
51
+ */
52
+ declare function qrByteCapacity(version: number, ecLevel: QrEcLevel): number;
53
+ /** Modules per side for a QR version: 17 + 4*version. */
54
+ declare function qrModuleCount(version: number): number;
55
+ /**
56
+ * Smallest QR version (1-40) whose BYTE-mode capacity at `ecLevel` fits
57
+ * `payloadByteLen`. Throws a clear Error when nothing fits.
58
+ *
59
+ * NOTE: this is the *byte* length, not the character length — callers holding
60
+ * a JS string should measure it with `new TextEncoder().encode(s).length`, not
61
+ * `s.length`, or every non-ASCII character will silently under-count.
62
+ */
63
+ declare function minQrVersion(payloadByteLen: number, ecLevel: QrEcLevel): number;
64
+ /** PNG data URL. Lazy-imports "qrcode" at call time (import-inert module). */
65
+ declare function generateQrDataUrl(value: string, opts?: QrOptions): Promise<string>;
66
+ /** SVG markup string. Same lazy import. */
67
+ declare function generateQrSvg(value: string, opts?: QrOptions): Promise<string>;
68
+ /**
69
+ * Short resolver URL — the platform's default e-commerce payload.
70
+ *
71
+ * The CODE VALUE IS AN OPAQUE INPUT: minting and uniqueness are server-side,
72
+ * never generated here. This function's whole job is to put a code the server
73
+ * already owns behind a URL short enough to stay in a low QR version.
74
+ *
75
+ * Result looks like `https://x.com/l/AB12cd`.
76
+ */
77
+ declare function buildShortResolverUrl(opts: {
78
+ origin: string;
79
+ code: string;
80
+ path?: string;
81
+ }): string;
82
+ /** GS1 Digital Link builder for supply-chain interoperability. */
83
+ declare function buildGs1DigitalLink(opts: {
84
+ origin: string;
85
+ gtin: string;
86
+ lot?: string;
87
+ serial?: string;
88
+ expiry?: string;
89
+ }): string;
90
+
91
+ export { DEFAULT_QR_EC_LEVEL, QR_MAX_VERSION, QR_MIN_QUIET_ZONE_MODULES, QR_MIN_VERSION, type QrEcLevel, type QrOptions, buildGs1DigitalLink, buildShortResolverUrl, generateQrDataUrl, generateQrSvg, minQrVersion, qrByteCapacity, qrModuleCount };
package/dist/qr.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @ai-matrx/print/qr — QR GENERATION.
3
+ *
4
+ * New code (no port source). Decoding is NOT here: it lives in
5
+ * `@ai-matrx/kit/qr`. This module only ever turns a value into a symbol, and
6
+ * builds the two payload shapes the platform actually prints:
7
+ *
8
+ * 1. a SHORT RESOLVER URL (`https://origin/l/<code>`) — the default
9
+ * e-commerce payload. The code value is an OPAQUE INPUT: minting,
10
+ * uniqueness, collision handling and lookup are all server-side concerns.
11
+ * Nothing in this file generates, randomizes, or validates the identity of
12
+ * a code beyond "it is a non-empty string we will percent-encode".
13
+ * 2. a GS1 DIGITAL LINK (`https://origin/01/<gtin14>[/10/<lot>][/21/<serial>][?17=YYMMDD]`)
14
+ * for supply-chain interoperability — the thing a retailer's scanner
15
+ * understands without knowing anything about AI Matrx.
16
+ *
17
+ * Two hard defaults that are NOT style choices:
18
+ * - Error correction defaults to "M", never "L". These end up on warehouse
19
+ * labels that get scuffed, wrapped around curved stock and read at an
20
+ * angle; "L" is only ever right for a huge, clean, flat code.
21
+ * - The quiet zone is CLAMPED to >= 4 modules (ISO/IEC 18004 §6.3.8 minimum)
22
+ * no matter what the caller passes. A tight quiet zone is the single most
23
+ * common cause of a label that scans on the design monitor and fails at the
24
+ * dock door.
25
+ *
26
+ * The module is import-inert: `qrcode` is only ever pulled in via
27
+ * `await import("qrcode")` inside a function body, so importing this file from
28
+ * a React Server Component (or any tree-shaken bundle that never draws a QR)
29
+ * costs nothing. No module-level mutable state, no React, no DOM at import
30
+ * time.
31
+ */
32
+ type QrEcLevel = "L" | "M" | "Q" | "H";
33
+ interface QrOptions {
34
+ /** Error correction. Default "M" — NEVER "L" (warehouse labels get scuffed; L is only right for huge clean codes). */
35
+ ecLevel?: QrEcLevel;
36
+ /** Quiet zone in modules. Default 4. CLAMPED to >= 4 at runtime — ISO 18004 spec minimum, not a style choice. */
37
+ quietZoneModules?: number;
38
+ /** Rendered width in pixels (dataURL/canvas lanes). */
39
+ widthPx?: number;
40
+ }
41
+ /** ISO/IEC 18004 §6.3.8 minimum quiet zone. Callers cannot go below this. */
42
+ declare const QR_MIN_QUIET_ZONE_MODULES = 4;
43
+ /** The default error correction level for every Matrx-printed code. */
44
+ declare const DEFAULT_QR_EC_LEVEL: QrEcLevel;
45
+ /** Lowest and highest QR symbol versions (ISO/IEC 18004). */
46
+ declare const QR_MIN_VERSION = 1;
47
+ declare const QR_MAX_VERSION = 40;
48
+ /**
49
+ * BYTE-mode capacity in bytes for a given version/level.
50
+ * Throws on an out-of-range version or unknown level.
51
+ */
52
+ declare function qrByteCapacity(version: number, ecLevel: QrEcLevel): number;
53
+ /** Modules per side for a QR version: 17 + 4*version. */
54
+ declare function qrModuleCount(version: number): number;
55
+ /**
56
+ * Smallest QR version (1-40) whose BYTE-mode capacity at `ecLevel` fits
57
+ * `payloadByteLen`. Throws a clear Error when nothing fits.
58
+ *
59
+ * NOTE: this is the *byte* length, not the character length — callers holding
60
+ * a JS string should measure it with `new TextEncoder().encode(s).length`, not
61
+ * `s.length`, or every non-ASCII character will silently under-count.
62
+ */
63
+ declare function minQrVersion(payloadByteLen: number, ecLevel: QrEcLevel): number;
64
+ /** PNG data URL. Lazy-imports "qrcode" at call time (import-inert module). */
65
+ declare function generateQrDataUrl(value: string, opts?: QrOptions): Promise<string>;
66
+ /** SVG markup string. Same lazy import. */
67
+ declare function generateQrSvg(value: string, opts?: QrOptions): Promise<string>;
68
+ /**
69
+ * Short resolver URL — the platform's default e-commerce payload.
70
+ *
71
+ * The CODE VALUE IS AN OPAQUE INPUT: minting and uniqueness are server-side,
72
+ * never generated here. This function's whole job is to put a code the server
73
+ * already owns behind a URL short enough to stay in a low QR version.
74
+ *
75
+ * Result looks like `https://x.com/l/AB12cd`.
76
+ */
77
+ declare function buildShortResolverUrl(opts: {
78
+ origin: string;
79
+ code: string;
80
+ path?: string;
81
+ }): string;
82
+ /** GS1 Digital Link builder for supply-chain interoperability. */
83
+ declare function buildGs1DigitalLink(opts: {
84
+ origin: string;
85
+ gtin: string;
86
+ lot?: string;
87
+ serial?: string;
88
+ expiry?: string;
89
+ }): string;
90
+
91
+ export { DEFAULT_QR_EC_LEVEL, QR_MAX_VERSION, QR_MIN_QUIET_ZONE_MODULES, QR_MIN_VERSION, type QrEcLevel, type QrOptions, buildGs1DigitalLink, buildShortResolverUrl, generateQrDataUrl, generateQrSvg, minQrVersion, qrByteCapacity, qrModuleCount };