@things-factory/board-service 10.1.34 → 10.1.37
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-server/controllers/label-command.js +30 -3
- package/dist-server/controllers/label-command.js.map +1 -1
- package/dist-server/controllers/label-command.test.js +55 -0
- package/dist-server/controllers/label-command.test.js.map +1 -1
- package/dist-server/routers/standalone-board-service-router.js +12 -0
- package/dist-server/routers/standalone-board-service-router.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -20,6 +20,7 @@ exports.cacheKey = cacheKey;
|
|
|
20
20
|
exports.getLabelCacheStats = getLabelCacheStats;
|
|
21
21
|
exports._resetLabelState = _resetLabelState;
|
|
22
22
|
exports._setBuildOverride = _setBuildOverride;
|
|
23
|
+
const crypto_1 = require("crypto");
|
|
23
24
|
const headless_pool_for_label_js_1 = require("./headless-pool-for-label.js");
|
|
24
25
|
const headless_model_js_1 = require("./headless-model.js");
|
|
25
26
|
// ── 캐시 / dedup state ─────────────────────────────────────────────────────
|
|
@@ -35,14 +36,40 @@ function fnv1a(s) {
|
|
|
35
36
|
}
|
|
36
37
|
return hash.toString(16);
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* **캐시 키가 담는 내용의 지문.** 32 비트 hash 로는 부족한 자리다.
|
|
41
|
+
*
|
|
42
|
+
* `fnv1a` 는 32 비트라 서로 다른 두 값이 같은 숫자를 낸다. 캐시 키에서 그것은 곧
|
|
43
|
+
* **다른 라벨이 인쇄되는 것**이다 — 값이 다른데 키가 같으면 앞서 만든 라벨이 그대로 나간다.
|
|
44
|
+
* 지어낸 이야기가 아니라 짧은 SKU 두 개로 실제로 난다(`{"sku":"967533"}` 와 `{"sku":"1488000"}` 이
|
|
45
|
+
* 둘 다 `97501f3d` 다 — 그 시험이 이 파일 곁에 있다).
|
|
46
|
+
*
|
|
47
|
+
* 키는 메모리 안에만 있으므로 길이가 문제되지 않는다. `fnv1a` 자체는 썸네일 스케줄러가 쓰고 있어
|
|
48
|
+
* 그대로 두되, **캐시 키에서는 쓰지 않는다.**
|
|
49
|
+
*/
|
|
50
|
+
function digest(s) {
|
|
51
|
+
return (0, crypto_1.createHash)('sha256').update(s).digest('hex');
|
|
52
|
+
}
|
|
38
53
|
function cacheKey(opts) {
|
|
54
|
+
/**
|
|
55
|
+
* **키의 첫 칸은 누가 물었는가다** (2026-09-22).
|
|
56
|
+
*
|
|
57
|
+
* 이것이 없으면 캐시가 테넌트를 넘는다. `labelcommand` 는 캐시를 **가장 먼저** 보고, 도메인을 보는
|
|
58
|
+
* 것은 그 뒤의 `buildLabelGrf` 뿐이다(`headlessModel` 이 `In([domain.id, domain.parentId])` 로 찾는다).
|
|
59
|
+
* 그래서 다른 테넌트가 같은 보드 id 로 물으면 **거절이어야 할 답이 앞 사람의 라벨로 나갔다.**
|
|
60
|
+
*
|
|
61
|
+
* 자식 도메인이 부모의 보드를 읽는 것은 정당하므로 도메인 하나로는 모자란다. 키는 **풀린 도메인
|
|
62
|
+
* 집합**을 담는다 — 같은 부모를 둔 자식 둘도 서로의 라벨을 받지 않는다.
|
|
63
|
+
*/
|
|
64
|
+
const domain = opts.context?.state?.domain;
|
|
65
|
+
const tenant = `t:${domain?.id ?? ''}>${domain?.parentId ?? ''}`;
|
|
39
66
|
// id 기반은 board update 가 있을 수 있으므로 TTL 짧게.
|
|
40
67
|
// model 직접 전달은 model 자체 hash — 변경 시 자동으로 새 key.
|
|
41
68
|
const tpl = opts.id
|
|
42
69
|
? `id:${opts.id}:${opts.draft ? 'd' : 'r'}`
|
|
43
|
-
: `m:${
|
|
44
|
-
const dataKey = opts.data ?
|
|
45
|
-
return `${tpl}|${dataKey}|${opts.orientation ?? ''}|${opts.mirror ? 1 : 0}|${opts.upsideDown ? 1 : 0}`;
|
|
70
|
+
: `m:${digest(typeof opts.model === 'string' ? opts.model : JSON.stringify(opts.model ?? null))}`;
|
|
71
|
+
const dataKey = opts.data ? digest(JSON.stringify(opts.data)) : '_';
|
|
72
|
+
return `${tenant}|${tpl}|${dataKey}|${opts.orientation ?? ''}|${opts.mirror ? 1 : 0}|${opts.upsideDown ? 1 : 0}`;
|
|
46
73
|
}
|
|
47
74
|
function cacheGet(key) {
|
|
48
75
|
const entry = grfCache.get(key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label-command.js","sourceRoot":"","sources":["../../server/controllers/label-command.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Label command (ZPL/GRF) — 백엔드 헤드리스 *전용*. 클라이언트 캡처는 anti-aliasing\n * / sub-pixel / DPR 차이로 ZPL 1-bit 정확 비트맵과 호환 안 됨. 또한 자동 출고\n * 워크플로우는 클라이언트 없이 트리거되므로 헤드리스 외 옵션 없음.\n *\n * 비용을 줄이는 두 메커니즘:\n * 1) In-flight dedup: 동일 (template, data, orientation, mirror, upsideDown)\n * 요청이 진행 중이면 같은 Promise 를 공유 — 라벨 출력 큐에서 같은 라벨이\n * 동시 N 회 요청되어도 puppeteer evaluate 는 1 회.\n * 2) LRU GRF 캐시: 같은 입력에 대해 TTL 동안 GRF 재사용 — 대량 인쇄에서\n * 같은 라벨 연속 출력 시 puppeteer 호출 0 회.\n */\n\nimport { getHeadlessPool } from './headless-pool-for-label.js'\nimport { headlessModel } from './headless-model.js'\n\nexport interface LabelCommandOptions {\n id?: string\n model?: any\n data?: any\n orientation?: string\n mirror?: boolean\n upsideDown?: boolean\n context: any\n draft?: boolean\n}\n\n// ── 캐시 / dedup state ─────────────────────────────────────────────────────\n\nconst GRF_CACHE_MAX = 200 // 최근 200 개 라벨 유지\nconst GRF_CACHE_TTL_MS = 60 * 1000 // 60 초 — id-기반 board update 도 1 분 내 반영\n\ninterface CacheEntry { value: string; expiresAt: number }\nconst grfCache = new Map<string, CacheEntry>()\nconst inFlight = new Map<string, Promise<string | undefined>>()\n\nexport function fnv1a(s: string): string {\n let hash = 2166136261\n for (let i = 0; i < s.length; i++) {\n hash ^= s.charCodeAt(i)\n hash = Math.imul(hash, 16777619) >>> 0\n }\n return hash.toString(16)\n}\n\nexport function cacheKey(opts: LabelCommandOptions): string {\n // id 기반은 board update 가 있을 수 있으므로 TTL 짧게.\n // model 직접 전달은 model 자체 hash — 변경 시 자동으로 새 key.\n const tpl = opts.id\n ? `id:${opts.id}:${opts.draft ? 'd' : 'r'}`\n : `m:${fnv1a(typeof opts.model === 'string' ? opts.model : JSON.stringify(opts.model ?? null))}`\n const dataKey = opts.data ? fnv1a(JSON.stringify(opts.data)) : '_'\n return `${tpl}|${dataKey}|${opts.orientation ?? ''}|${opts.mirror ? 1 : 0}|${opts.upsideDown ? 1 : 0}`\n}\n\nfunction cacheGet(key: string): string | undefined {\n const entry = grfCache.get(key)\n if (!entry) return undefined\n if (entry.expiresAt < Date.now()) {\n grfCache.delete(key)\n return undefined\n }\n // LRU refresh — 최근 사용을 Map 마지막으로 이동.\n grfCache.delete(key)\n grfCache.set(key, entry)\n return entry.value\n}\n\nfunction cacheSet(key: string, value: string): void {\n grfCache.delete(key)\n grfCache.set(key, { value, expiresAt: Date.now() + GRF_CACHE_TTL_MS })\n if (grfCache.size > GRF_CACHE_MAX) {\n const oldest = grfCache.keys().next().value\n if (oldest !== undefined) grfCache.delete(oldest)\n }\n}\n\n/** 진단 / 운영용 — 현재 cache hit ratio 등 모니터링 가능. */\nexport function getLabelCacheStats() {\n return { cacheSize: grfCache.size, inFlight: inFlight.size }\n}\n\n/** Test-only — 캐시 / in-flight state 초기화. */\nexport function _resetLabelState(): void {\n grfCache.clear()\n inFlight.clear()\n}\n\n/**\n * Test-only — buildLabelGrf 를 mock 으로 교체. production 에서는 항상 default 사용.\n */\nlet _buildOverride: ((opts: LabelCommandOptions) => Promise<string | undefined>) | null = null\nexport function _setBuildOverride(fn: ((opts: LabelCommandOptions) => Promise<string | undefined>) | null): void {\n _buildOverride = fn\n}\n\n// ── 헤드리스 evaluate (실제 puppeteer 호출). ──────────────────────────────────\n\nasync function buildLabelGrf(opts: LabelCommandOptions): Promise<string | undefined> {\n const { domain } = opts.context.state\n\n const browser = (await getHeadlessPool().acquire()) as any\n if (!browser) return undefined\n\n try {\n const { page } = browser\n\n const { model: resolvedModel } = await headlessModel(\n { domain, model: opts.model, id: opts.id },\n !!opts.draft\n )\n\n const grf = await page.evaluate(\n async (model, data, orientation, mirror, upsideDown) => {\n //@ts-ignore\n let s = createScene(model)\n if (data) {\n s.data = data\n }\n return new Promise(resolve => {\n // @ts-ignore\n requestAnimationFrame(() => {\n // @ts-ignore\n let grf = imageDataToGrf(s, model, orientation, mirror, upsideDown)\n resolve(grf)\n // @ts-ignore\n sceneContainer.removeChild(s.target)\n s.dispose()\n })\n })\n },\n resolvedModel,\n opts.data,\n opts.orientation,\n opts.mirror,\n opts.upsideDown\n )\n\n return `\n^XA\n^GFA,${grf}\n^FS\n^XZ`\n } finally {\n // 에러 발생 시에도 반드시 풀에 브라우저를 반환\n getHeadlessPool().release(browser)\n }\n}\n\n/**\n * 라벨 출력 — ZPL/GRF 문자열 반환.\n *\n * @param {String} id 모델 ID\n * @param {Object} data 매핑할 데이터\n * @param {String} orientation (시계방향) N: 0, R: 90, I: 180, B: 270\n * @param {boolean} mirror 좌우반전\n * @param {boolean} upsideDown 상하반전\n */\nexport const labelcommand = async (opts: LabelCommandOptions): Promise<string | undefined> => {\n const key = cacheKey(opts)\n\n // 1) GRF LRU 캐시 — 같은 입력 반복 출력 시 puppeteer 호출 0.\n const cached = cacheGet(key)\n if (cached !== undefined) return cached\n\n // 2) In-flight dedup — 동일 요청이 이미 진행 중이면 같은 Promise share.\n const existing = inFlight.get(key)\n if (existing) return existing\n\n const builder = _buildOverride ?? buildLabelGrf\n const promise = builder(opts)\n .then(result => {\n if (result) cacheSet(key, result)\n return result\n })\n .finally(() => {\n inFlight.delete(key)\n })\n\n inFlight.set(key, promise)\n return promise\n}\n"]}
|
|
1
|
+
{"version":3,"file":"label-command.js","sourceRoot":"","sources":["../../server/controllers/label-command.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Label command (ZPL/GRF) — 백엔드 헤드리스 *전용*. 클라이언트 캡처는 anti-aliasing\n * / sub-pixel / DPR 차이로 ZPL 1-bit 정확 비트맵과 호환 안 됨. 또한 자동 출고\n * 워크플로우는 클라이언트 없이 트리거되므로 헤드리스 외 옵션 없음.\n *\n * 비용을 줄이는 두 메커니즘:\n * 1) In-flight dedup: 동일 (template, data, orientation, mirror, upsideDown)\n * 요청이 진행 중이면 같은 Promise 를 공유 — 라벨 출력 큐에서 같은 라벨이\n * 동시 N 회 요청되어도 puppeteer evaluate 는 1 회.\n * 2) LRU GRF 캐시: 같은 입력에 대해 TTL 동안 GRF 재사용 — 대량 인쇄에서\n * 같은 라벨 연속 출력 시 puppeteer 호출 0 회.\n */\n\nimport { createHash } from 'crypto'\n\nimport { getHeadlessPool } from './headless-pool-for-label.js'\nimport { headlessModel } from './headless-model.js'\n\nexport interface LabelCommandOptions {\n id?: string\n model?: any\n data?: any\n orientation?: string\n mirror?: boolean\n upsideDown?: boolean\n context: any\n draft?: boolean\n}\n\n// ── 캐시 / dedup state ─────────────────────────────────────────────────────\n\nconst GRF_CACHE_MAX = 200 // 최근 200 개 라벨 유지\nconst GRF_CACHE_TTL_MS = 60 * 1000 // 60 초 — id-기반 board update 도 1 분 내 반영\n\ninterface CacheEntry { value: string; expiresAt: number }\nconst grfCache = new Map<string, CacheEntry>()\nconst inFlight = new Map<string, Promise<string | undefined>>()\n\nexport function fnv1a(s: string): string {\n let hash = 2166136261\n for (let i = 0; i < s.length; i++) {\n hash ^= s.charCodeAt(i)\n hash = Math.imul(hash, 16777619) >>> 0\n }\n return hash.toString(16)\n}\n\n/**\n * **캐시 키가 담는 내용의 지문.** 32 비트 hash 로는 부족한 자리다.\n *\n * `fnv1a` 는 32 비트라 서로 다른 두 값이 같은 숫자를 낸다. 캐시 키에서 그것은 곧\n * **다른 라벨이 인쇄되는 것**이다 — 값이 다른데 키가 같으면 앞서 만든 라벨이 그대로 나간다.\n * 지어낸 이야기가 아니라 짧은 SKU 두 개로 실제로 난다(`{\"sku\":\"967533\"}` 와 `{\"sku\":\"1488000\"}` 이\n * 둘 다 `97501f3d` 다 — 그 시험이 이 파일 곁에 있다).\n *\n * 키는 메모리 안에만 있으므로 길이가 문제되지 않는다. `fnv1a` 자체는 썸네일 스케줄러가 쓰고 있어\n * 그대로 두되, **캐시 키에서는 쓰지 않는다.**\n */\nfunction digest(s: string): string {\n return createHash('sha256').update(s).digest('hex')\n}\n\nexport function cacheKey(opts: LabelCommandOptions): string {\n /**\n * **키의 첫 칸은 누가 물었는가다** (2026-09-22).\n *\n * 이것이 없으면 캐시가 테넌트를 넘는다. `labelcommand` 는 캐시를 **가장 먼저** 보고, 도메인을 보는\n * 것은 그 뒤의 `buildLabelGrf` 뿐이다(`headlessModel` 이 `In([domain.id, domain.parentId])` 로 찾는다).\n * 그래서 다른 테넌트가 같은 보드 id 로 물으면 **거절이어야 할 답이 앞 사람의 라벨로 나갔다.**\n *\n * 자식 도메인이 부모의 보드를 읽는 것은 정당하므로 도메인 하나로는 모자란다. 키는 **풀린 도메인\n * 집합**을 담는다 — 같은 부모를 둔 자식 둘도 서로의 라벨을 받지 않는다.\n */\n const domain = opts.context?.state?.domain\n const tenant = `t:${domain?.id ?? ''}>${domain?.parentId ?? ''}`\n // id 기반은 board update 가 있을 수 있으므로 TTL 짧게.\n // model 직접 전달은 model 자체 hash — 변경 시 자동으로 새 key.\n const tpl = opts.id\n ? `id:${opts.id}:${opts.draft ? 'd' : 'r'}`\n : `m:${digest(typeof opts.model === 'string' ? opts.model : JSON.stringify(opts.model ?? null))}`\n const dataKey = opts.data ? digest(JSON.stringify(opts.data)) : '_'\n return `${tenant}|${tpl}|${dataKey}|${opts.orientation ?? ''}|${opts.mirror ? 1 : 0}|${opts.upsideDown ? 1 : 0}`\n}\n\nfunction cacheGet(key: string): string | undefined {\n const entry = grfCache.get(key)\n if (!entry) return undefined\n if (entry.expiresAt < Date.now()) {\n grfCache.delete(key)\n return undefined\n }\n // LRU refresh — 최근 사용을 Map 마지막으로 이동.\n grfCache.delete(key)\n grfCache.set(key, entry)\n return entry.value\n}\n\nfunction cacheSet(key: string, value: string): void {\n grfCache.delete(key)\n grfCache.set(key, { value, expiresAt: Date.now() + GRF_CACHE_TTL_MS })\n if (grfCache.size > GRF_CACHE_MAX) {\n const oldest = grfCache.keys().next().value\n if (oldest !== undefined) grfCache.delete(oldest)\n }\n}\n\n/** 진단 / 운영용 — 현재 cache hit ratio 등 모니터링 가능. */\nexport function getLabelCacheStats() {\n return { cacheSize: grfCache.size, inFlight: inFlight.size }\n}\n\n/** Test-only — 캐시 / in-flight state 초기화. */\nexport function _resetLabelState(): void {\n grfCache.clear()\n inFlight.clear()\n}\n\n/**\n * Test-only — buildLabelGrf 를 mock 으로 교체. production 에서는 항상 default 사용.\n */\nlet _buildOverride: ((opts: LabelCommandOptions) => Promise<string | undefined>) | null = null\nexport function _setBuildOverride(fn: ((opts: LabelCommandOptions) => Promise<string | undefined>) | null): void {\n _buildOverride = fn\n}\n\n// ── 헤드리스 evaluate (실제 puppeteer 호출). ──────────────────────────────────\n\nasync function buildLabelGrf(opts: LabelCommandOptions): Promise<string | undefined> {\n const { domain } = opts.context.state\n\n const browser = (await getHeadlessPool().acquire()) as any\n if (!browser) return undefined\n\n try {\n const { page } = browser\n\n const { model: resolvedModel } = await headlessModel(\n { domain, model: opts.model, id: opts.id },\n !!opts.draft\n )\n\n const grf = await page.evaluate(\n async (model, data, orientation, mirror, upsideDown) => {\n //@ts-ignore\n let s = createScene(model)\n if (data) {\n s.data = data\n }\n return new Promise(resolve => {\n // @ts-ignore\n requestAnimationFrame(() => {\n // @ts-ignore\n let grf = imageDataToGrf(s, model, orientation, mirror, upsideDown)\n resolve(grf)\n // @ts-ignore\n sceneContainer.removeChild(s.target)\n s.dispose()\n })\n })\n },\n resolvedModel,\n opts.data,\n opts.orientation,\n opts.mirror,\n opts.upsideDown\n )\n\n return `\n^XA\n^GFA,${grf}\n^FS\n^XZ`\n } finally {\n // 에러 발생 시에도 반드시 풀에 브라우저를 반환\n getHeadlessPool().release(browser)\n }\n}\n\n/**\n * 라벨 출력 — ZPL/GRF 문자열 반환.\n *\n * @param {String} id 모델 ID\n * @param {Object} data 매핑할 데이터\n * @param {String} orientation (시계방향) N: 0, R: 90, I: 180, B: 270\n * @param {boolean} mirror 좌우반전\n * @param {boolean} upsideDown 상하반전\n */\nexport const labelcommand = async (opts: LabelCommandOptions): Promise<string | undefined> => {\n const key = cacheKey(opts)\n\n // 1) GRF LRU 캐시 — 같은 입력 반복 출력 시 puppeteer 호출 0.\n const cached = cacheGet(key)\n if (cached !== undefined) return cached\n\n // 2) In-flight dedup — 동일 요청이 이미 진행 중이면 같은 Promise share.\n const existing = inFlight.get(key)\n if (existing) return existing\n\n const builder = _buildOverride ?? buildLabelGrf\n const promise = builder(opts)\n .then(result => {\n if (result) cacheSet(key, result)\n return result\n })\n .finally(() => {\n inFlight.delete(key)\n })\n\n inFlight.set(key, promise)\n return promise\n}\n"]}
|
|
@@ -208,4 +208,59 @@ describe('labelcommand — cache + in-flight dedup', () => {
|
|
|
208
208
|
expect(new Set(results).size).toBe(1);
|
|
209
209
|
});
|
|
210
210
|
});
|
|
211
|
+
// ─── 캐시가 테넌트를 넘지 않는다 (2026-09-22) ────────────────────────────────
|
|
212
|
+
describe('label cache is scoped to who asked', () => {
|
|
213
|
+
const asDomain = (id, parentId) => ({ state: { domain: { id, parentId }, user: { id: 'u1' } } });
|
|
214
|
+
test('같은 보드 id 를 다른 테넌트가 물으면 키가 다르다', () => {
|
|
215
|
+
const a = (0, label_command_1.cacheKey)({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a') });
|
|
216
|
+
const b = (0, label_command_1.cacheKey)({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-b') });
|
|
217
|
+
expect(a).not.toBe(b);
|
|
218
|
+
});
|
|
219
|
+
test('같은 부모를 둔 자식 둘도 서로의 라벨을 받지 않는다', () => {
|
|
220
|
+
const a = (0, label_command_1.cacheKey)({ id: 'board-1', data: { x: 1 }, context: asDomain('child-a', 'parent') });
|
|
221
|
+
const b = (0, label_command_1.cacheKey)({ id: 'board-1', data: { x: 1 }, context: asDomain('child-b', 'parent') });
|
|
222
|
+
expect(a).not.toBe(b);
|
|
223
|
+
});
|
|
224
|
+
test('같은 테넌트의 같은 요청은 그대로 한 키다 — 캐시가 죽지 않았다', () => {
|
|
225
|
+
const a = (0, label_command_1.cacheKey)({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a', 'parent') });
|
|
226
|
+
const b = (0, label_command_1.cacheKey)({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a', 'parent') });
|
|
227
|
+
expect(a).toBe(b);
|
|
228
|
+
});
|
|
229
|
+
test('캐시를 지난 답이 다른 테넌트에게 안 간다', async () => {
|
|
230
|
+
(0, label_command_1._resetLabelState)();
|
|
231
|
+
let built = 0;
|
|
232
|
+
(0, label_command_1._setBuildOverride)(async (opts) => {
|
|
233
|
+
built++;
|
|
234
|
+
return `^XA label for ${opts.context.state.domain.id} ^XZ`;
|
|
235
|
+
});
|
|
236
|
+
try {
|
|
237
|
+
const first = await (0, label_command_1.labelcommand)({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a') });
|
|
238
|
+
const second = await (0, label_command_1.labelcommand)({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-b') });
|
|
239
|
+
expect(first).toBe('^XA label for tenant-a ^XZ');
|
|
240
|
+
expect(second).toBe('^XA label for tenant-b ^XZ');
|
|
241
|
+
expect(built).toBe(2);
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
(0, label_command_1._setBuildOverride)(null);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
// ─── 32 비트 hash 는 다른 라벨을 인쇄한다 (2026-09-22) ───────────────────────
|
|
249
|
+
describe('the data fingerprint does not collide into another label', () => {
|
|
250
|
+
/*
|
|
251
|
+
* 지어낸 값이 아니다. 짧은 SKU 두 개를 전수로 훑어 찾은 실제 fnv1a-32 충돌이다.
|
|
252
|
+
* 캐시 키가 이 hash 를 쓰던 동안, 둘 중 먼저 인쇄한 라벨이 나머지 하나에도 나갔다.
|
|
253
|
+
*/
|
|
254
|
+
const COLLIDING_A = { sku: '967533' };
|
|
255
|
+
const COLLIDING_B = { sku: '1488000' };
|
|
256
|
+
test('그 둘이 정말 fnv1a 에서 같은 값이다', () => {
|
|
257
|
+
expect((0, label_command_1.fnv1a)(JSON.stringify(COLLIDING_A))).toBe((0, label_command_1.fnv1a)(JSON.stringify(COLLIDING_B)));
|
|
258
|
+
});
|
|
259
|
+
test('그런데도 캐시 키는 갈린다', () => {
|
|
260
|
+
const ctx2 = { state: { domain: { id: 'tenant-a' }, user: { id: 'u1' } } };
|
|
261
|
+
const a = (0, label_command_1.cacheKey)({ id: 'board-1', data: COLLIDING_A, context: ctx2 });
|
|
262
|
+
const b = (0, label_command_1.cacheKey)({ id: 'board-1', data: COLLIDING_B, context: ctx2 });
|
|
263
|
+
expect(a).not.toBe(b);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
211
266
|
//# sourceMappingURL=label-command.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label-command.test.js","sourceRoot":"","sources":["../../server/controllers/label-command.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Label Command — in-flight dedup + LRU GRF cache 단위 테스트.\n *\n * puppeteer 의존성은 _setBuildOverride 로 mock. 검증 대상:\n * 1) cacheKey — 같은 입력 같은 키 / 다른 입력 다른 키 / 정규화\n * 2) cache hit — 같은 (template, data, ...) 반복 호출 시 puppeteer 0 회\n * 3) in-flight dedup — 동일 요청 동시 진행 시 build 함수 1 회만, 모든 caller 가 같은 결과\n * 4) cache TTL — 만료된 entry 는 새로 build\n * 5) LRU eviction — 캐시 가득 차면 오래된 것부터 제거\n * 6) build 실패 시 캐시 안 됨 → 다음 호출에서 재시도\n * 7) id 와 model 별 다른 키 공간\n */\n\n// import 시점에 끌려오는 무거운 deps (headless pool, headless model) 은 어차피\n// _setBuildOverride 로 교체하므로 module load 만 가능하게 mock.\njest.mock('./headless-pool-for-label', () => ({\n getHeadlessPool: () => ({ acquire: jest.fn(), release: jest.fn() })\n}))\njest.mock('./headless-model', () => ({\n headlessModel: async () => ({ model: {} })\n}))\n\nimport {\n fnv1a,\n cacheKey,\n labelcommand,\n _setBuildOverride,\n _resetLabelState,\n getLabelCacheStats,\n type LabelCommandOptions\n} from './label-command'\n\nconst ctx = { state: { domain: { subdomain: 'test' }, user: { id: 'u1' } } }\n\n// ─── fnv1a ──────────────────────────────────────────────────────────────────\n\ndescribe('label fnv1a hash', () => {\n test('결정성', () => {\n expect(fnv1a('foo')).toBe(fnv1a('foo'))\n })\n\n test('충돌 적음 — 50 개 입력', () => {\n const hashes = new Set<string>()\n for (let i = 0; i < 50; i++) {\n hashes.add(fnv1a(`label-${i}-{\"data\":${i}}`))\n }\n expect(hashes.size).toBe(50)\n })\n})\n\n// ─── cacheKey ───────────────────────────────────────────────────────────────\n\ndescribe('cacheKey 생성', () => {\n test('같은 입력 → 같은 key', () => {\n const opts: LabelCommandOptions = { id: 'lbl1', data: { x: 1 }, orientation: 'N', context: ctx }\n expect(cacheKey(opts)).toBe(cacheKey(opts))\n })\n\n test('id 다르면 다른 key', () => {\n const a = cacheKey({ id: 'lbl1', data: { x: 1 }, context: ctx })\n const b = cacheKey({ id: 'lbl2', data: { x: 1 }, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('draft / released 분리', () => {\n const a = cacheKey({ id: 'lbl1', data: {}, draft: false, context: ctx })\n const b = cacheKey({ id: 'lbl1', data: {}, draft: true, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('orientation 다르면 다른 key', () => {\n const a = cacheKey({ id: 'lbl1', data: {}, orientation: 'N', context: ctx })\n const b = cacheKey({ id: 'lbl1', data: {}, orientation: 'R', context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('mirror / upsideDown flag 가 key 에 반영', () => {\n const base = { id: 'lbl1', data: {}, context: ctx }\n const a = cacheKey(base)\n const b = cacheKey({ ...base, mirror: true })\n const c = cacheKey({ ...base, upsideDown: true })\n const d = cacheKey({ ...base, mirror: true, upsideDown: true })\n\n expect(new Set([a, b, c, d]).size).toBe(4)\n })\n\n test('data 변경 시 다른 key', () => {\n const a = cacheKey({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n const b = cacheKey({ id: 'lbl1', data: { sn: 'A2' }, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('data 가 deep 동일하면 같은 key (key 순서 무관)', () => {\n const a = cacheKey({ id: 'lbl1', data: { x: 1, y: 2 }, context: ctx })\n const b = cacheKey({ id: 'lbl1', data: { x: 1, y: 2 }, context: ctx })\n expect(a).toBe(b)\n })\n\n test('id 와 model 직접 전달은 다른 key 공간', () => {\n const a = cacheKey({ id: 'lbl1', data: {}, context: ctx })\n const b = cacheKey({ model: { template: 'abc' }, data: {}, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('model JSON 내용이 같으면 같은 key (id 없을 때)', () => {\n const a = cacheKey({ model: { width: 100 }, data: {}, context: ctx })\n const b = cacheKey({ model: { width: 100 }, data: {}, context: ctx })\n expect(a).toBe(b)\n })\n})\n\n// ─── labelcommand: cache + dedup ────────────────────────────────────────────\n\ndescribe('labelcommand — cache + in-flight dedup', () => {\n let buildCalls: LabelCommandOptions[]\n let nextResult: string | undefined\n let buildShouldFail: boolean\n let buildDelay: number\n\n beforeEach(() => {\n buildCalls = []\n nextResult = '^XA\\n^GFA,abc\\n^FS\\n^XZ'\n buildShouldFail = false\n buildDelay = 0\n\n _setBuildOverride(async opts => {\n buildCalls.push(opts)\n if (buildDelay > 0) {\n await new Promise(r => setTimeout(r, buildDelay))\n }\n if (buildShouldFail) throw new Error('build failed')\n return nextResult\n })\n _resetLabelState()\n })\n\n afterEach(() => {\n _setBuildOverride(null)\n _resetLabelState()\n })\n\n test('첫 호출 → build 1 회, 결과 반환', async () => {\n const result = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(1)\n expect(result).toBe('^XA\\n^GFA,abc\\n^FS\\n^XZ')\n })\n\n test('동일 요청 두 번 sequential → 두 번째는 cache hit', async () => {\n await labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n expect(buildCalls).toHaveLength(1)\n\n const second = await labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n expect(buildCalls).toHaveLength(1) // cache hit\n expect(second).toBe('^XA\\n^GFA,abc\\n^FS\\n^XZ')\n })\n\n test('동일 요청 동시 100 회 → in-flight dedup 으로 build 1 회', async () => {\n buildDelay = 50\n\n const promises = Array.from({ length: 100 }, () =>\n labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n )\n\n const results = await Promise.all(promises)\n expect(buildCalls).toHaveLength(1)\n // 모든 caller 가 같은 결과\n expect(new Set(results).size).toBe(1)\n })\n\n test('다른 요청은 별도 build', async () => {\n await labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n await labelcommand({ id: 'lbl1', data: { sn: 'A2' }, context: ctx })\n await labelcommand({ id: 'lbl2', data: { sn: 'A1' }, context: ctx })\n\n expect(buildCalls).toHaveLength(3)\n })\n\n test('build 실패 시 cache 안 됨 → 다음 호출에서 재시도', async () => {\n buildShouldFail = true\n await expect(\n labelcommand({ id: 'lbl1', data: {}, context: ctx })\n ).rejects.toThrow('build failed')\n expect(buildCalls).toHaveLength(1)\n\n buildShouldFail = false\n const result = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(2)\n expect(result).toBe('^XA\\n^GFA,abc\\n^FS\\n^XZ')\n })\n\n test('build undefined 반환 시 cache 안 됨', async () => {\n nextResult = undefined\n const r1 = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(r1).toBeUndefined()\n expect(buildCalls).toHaveLength(1)\n\n // 같은 입력 다시 — cache 없으므로 또 build\n const r2 = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(2)\n })\n\n test('orientation 다르면 별도 cache', async () => {\n await labelcommand({ id: 'lbl1', data: {}, orientation: 'N', context: ctx })\n await labelcommand({ id: 'lbl1', data: {}, orientation: 'R', context: ctx })\n await labelcommand({ id: 'lbl1', data: {}, orientation: 'N', context: ctx })\n\n expect(buildCalls).toHaveLength(2) // N, R 각각 1 회. N 의 두 번째는 cache hit\n })\n\n test('대량 인쇄 시나리오 — 같은 라벨 200 장 출력 시 build 1 회', async () => {\n const params = { id: 'lbl-mass', data: { common: 'data' }, orientation: 'N', context: ctx }\n\n for (let i = 0; i < 200; i++) {\n await labelcommand(params)\n }\n\n expect(buildCalls).toHaveLength(1)\n })\n\n test('각 라벨이 다른 시리얼 번호인 시나리오 — 매번 build', async () => {\n for (let i = 0; i < 10; i++) {\n await labelcommand({\n id: 'lbl-serial',\n data: { sn: `SN-${i}` },\n context: ctx\n })\n }\n expect(buildCalls).toHaveLength(10)\n })\n\n test('cache stats 가 합리적 값 반환', async () => {\n const before = getLabelCacheStats()\n expect(before.cacheSize).toBe(0)\n\n await labelcommand({ id: 'lbl1', data: { x: 1 }, context: ctx })\n await labelcommand({ id: 'lbl2', data: { x: 1 }, context: ctx })\n\n const after = getLabelCacheStats()\n expect(after.cacheSize).toBe(2)\n expect(after.inFlight).toBe(0) // 완료 후\n })\n\n test('in-flight 동안 stats.inFlight 증가', async () => {\n buildDelay = 100\n\n const promise = labelcommand({ id: 'lbl1', data: {}, context: ctx })\n // 시작 직후 — buildDelay 안에\n await new Promise(r => setImmediate(r))\n const during = getLabelCacheStats()\n expect(during.inFlight).toBeGreaterThanOrEqual(1)\n\n await promise\n const after = getLabelCacheStats()\n expect(after.inFlight).toBe(0)\n })\n\n test('성공 캐시 후 동일 요청 100 회 동시 → cache hit 만 (build 0 회 추가)', async () => {\n // 첫 호출로 캐시 채움\n await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(1)\n\n // 동시 100 회 — 모두 cache hit\n const results = await Promise.all(\n Array.from({ length: 100 }, () => labelcommand({ id: 'lbl1', data: {}, context: ctx }))\n )\n expect(buildCalls).toHaveLength(1)\n expect(new Set(results).size).toBe(1)\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"label-command.test.js","sourceRoot":"","sources":["../../server/controllers/label-command.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Label Command — in-flight dedup + LRU GRF cache 단위 테스트.\n *\n * puppeteer 의존성은 _setBuildOverride 로 mock. 검증 대상:\n * 1) cacheKey — 같은 입력 같은 키 / 다른 입력 다른 키 / 정규화\n * 2) cache hit — 같은 (template, data, ...) 반복 호출 시 puppeteer 0 회\n * 3) in-flight dedup — 동일 요청 동시 진행 시 build 함수 1 회만, 모든 caller 가 같은 결과\n * 4) cache TTL — 만료된 entry 는 새로 build\n * 5) LRU eviction — 캐시 가득 차면 오래된 것부터 제거\n * 6) build 실패 시 캐시 안 됨 → 다음 호출에서 재시도\n * 7) id 와 model 별 다른 키 공간\n */\n\n// import 시점에 끌려오는 무거운 deps (headless pool, headless model) 은 어차피\n// _setBuildOverride 로 교체하므로 module load 만 가능하게 mock.\njest.mock('./headless-pool-for-label', () => ({\n getHeadlessPool: () => ({ acquire: jest.fn(), release: jest.fn() })\n}))\njest.mock('./headless-model', () => ({\n headlessModel: async () => ({ model: {} })\n}))\n\nimport {\n fnv1a,\n cacheKey,\n labelcommand,\n _setBuildOverride,\n _resetLabelState,\n getLabelCacheStats,\n type LabelCommandOptions\n} from './label-command'\n\nconst ctx = { state: { domain: { subdomain: 'test' }, user: { id: 'u1' } } }\n\n// ─── fnv1a ──────────────────────────────────────────────────────────────────\n\ndescribe('label fnv1a hash', () => {\n test('결정성', () => {\n expect(fnv1a('foo')).toBe(fnv1a('foo'))\n })\n\n test('충돌 적음 — 50 개 입력', () => {\n const hashes = new Set<string>()\n for (let i = 0; i < 50; i++) {\n hashes.add(fnv1a(`label-${i}-{\"data\":${i}}`))\n }\n expect(hashes.size).toBe(50)\n })\n})\n\n// ─── cacheKey ───────────────────────────────────────────────────────────────\n\ndescribe('cacheKey 생성', () => {\n test('같은 입력 → 같은 key', () => {\n const opts: LabelCommandOptions = { id: 'lbl1', data: { x: 1 }, orientation: 'N', context: ctx }\n expect(cacheKey(opts)).toBe(cacheKey(opts))\n })\n\n test('id 다르면 다른 key', () => {\n const a = cacheKey({ id: 'lbl1', data: { x: 1 }, context: ctx })\n const b = cacheKey({ id: 'lbl2', data: { x: 1 }, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('draft / released 분리', () => {\n const a = cacheKey({ id: 'lbl1', data: {}, draft: false, context: ctx })\n const b = cacheKey({ id: 'lbl1', data: {}, draft: true, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('orientation 다르면 다른 key', () => {\n const a = cacheKey({ id: 'lbl1', data: {}, orientation: 'N', context: ctx })\n const b = cacheKey({ id: 'lbl1', data: {}, orientation: 'R', context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('mirror / upsideDown flag 가 key 에 반영', () => {\n const base = { id: 'lbl1', data: {}, context: ctx }\n const a = cacheKey(base)\n const b = cacheKey({ ...base, mirror: true })\n const c = cacheKey({ ...base, upsideDown: true })\n const d = cacheKey({ ...base, mirror: true, upsideDown: true })\n\n expect(new Set([a, b, c, d]).size).toBe(4)\n })\n\n test('data 변경 시 다른 key', () => {\n const a = cacheKey({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n const b = cacheKey({ id: 'lbl1', data: { sn: 'A2' }, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('data 가 deep 동일하면 같은 key (key 순서 무관)', () => {\n const a = cacheKey({ id: 'lbl1', data: { x: 1, y: 2 }, context: ctx })\n const b = cacheKey({ id: 'lbl1', data: { x: 1, y: 2 }, context: ctx })\n expect(a).toBe(b)\n })\n\n test('id 와 model 직접 전달은 다른 key 공간', () => {\n const a = cacheKey({ id: 'lbl1', data: {}, context: ctx })\n const b = cacheKey({ model: { template: 'abc' }, data: {}, context: ctx })\n expect(a).not.toBe(b)\n })\n\n test('model JSON 내용이 같으면 같은 key (id 없을 때)', () => {\n const a = cacheKey({ model: { width: 100 }, data: {}, context: ctx })\n const b = cacheKey({ model: { width: 100 }, data: {}, context: ctx })\n expect(a).toBe(b)\n })\n})\n\n// ─── labelcommand: cache + dedup ────────────────────────────────────────────\n\ndescribe('labelcommand — cache + in-flight dedup', () => {\n let buildCalls: LabelCommandOptions[]\n let nextResult: string | undefined\n let buildShouldFail: boolean\n let buildDelay: number\n\n beforeEach(() => {\n buildCalls = []\n nextResult = '^XA\\n^GFA,abc\\n^FS\\n^XZ'\n buildShouldFail = false\n buildDelay = 0\n\n _setBuildOverride(async opts => {\n buildCalls.push(opts)\n if (buildDelay > 0) {\n await new Promise(r => setTimeout(r, buildDelay))\n }\n if (buildShouldFail) throw new Error('build failed')\n return nextResult\n })\n _resetLabelState()\n })\n\n afterEach(() => {\n _setBuildOverride(null)\n _resetLabelState()\n })\n\n test('첫 호출 → build 1 회, 결과 반환', async () => {\n const result = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(1)\n expect(result).toBe('^XA\\n^GFA,abc\\n^FS\\n^XZ')\n })\n\n test('동일 요청 두 번 sequential → 두 번째는 cache hit', async () => {\n await labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n expect(buildCalls).toHaveLength(1)\n\n const second = await labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n expect(buildCalls).toHaveLength(1) // cache hit\n expect(second).toBe('^XA\\n^GFA,abc\\n^FS\\n^XZ')\n })\n\n test('동일 요청 동시 100 회 → in-flight dedup 으로 build 1 회', async () => {\n buildDelay = 50\n\n const promises = Array.from({ length: 100 }, () =>\n labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n )\n\n const results = await Promise.all(promises)\n expect(buildCalls).toHaveLength(1)\n // 모든 caller 가 같은 결과\n expect(new Set(results).size).toBe(1)\n })\n\n test('다른 요청은 별도 build', async () => {\n await labelcommand({ id: 'lbl1', data: { sn: 'A1' }, context: ctx })\n await labelcommand({ id: 'lbl1', data: { sn: 'A2' }, context: ctx })\n await labelcommand({ id: 'lbl2', data: { sn: 'A1' }, context: ctx })\n\n expect(buildCalls).toHaveLength(3)\n })\n\n test('build 실패 시 cache 안 됨 → 다음 호출에서 재시도', async () => {\n buildShouldFail = true\n await expect(\n labelcommand({ id: 'lbl1', data: {}, context: ctx })\n ).rejects.toThrow('build failed')\n expect(buildCalls).toHaveLength(1)\n\n buildShouldFail = false\n const result = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(2)\n expect(result).toBe('^XA\\n^GFA,abc\\n^FS\\n^XZ')\n })\n\n test('build undefined 반환 시 cache 안 됨', async () => {\n nextResult = undefined\n const r1 = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(r1).toBeUndefined()\n expect(buildCalls).toHaveLength(1)\n\n // 같은 입력 다시 — cache 없으므로 또 build\n const r2 = await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(2)\n })\n\n test('orientation 다르면 별도 cache', async () => {\n await labelcommand({ id: 'lbl1', data: {}, orientation: 'N', context: ctx })\n await labelcommand({ id: 'lbl1', data: {}, orientation: 'R', context: ctx })\n await labelcommand({ id: 'lbl1', data: {}, orientation: 'N', context: ctx })\n\n expect(buildCalls).toHaveLength(2) // N, R 각각 1 회. N 의 두 번째는 cache hit\n })\n\n test('대량 인쇄 시나리오 — 같은 라벨 200 장 출력 시 build 1 회', async () => {\n const params = { id: 'lbl-mass', data: { common: 'data' }, orientation: 'N', context: ctx }\n\n for (let i = 0; i < 200; i++) {\n await labelcommand(params)\n }\n\n expect(buildCalls).toHaveLength(1)\n })\n\n test('각 라벨이 다른 시리얼 번호인 시나리오 — 매번 build', async () => {\n for (let i = 0; i < 10; i++) {\n await labelcommand({\n id: 'lbl-serial',\n data: { sn: `SN-${i}` },\n context: ctx\n })\n }\n expect(buildCalls).toHaveLength(10)\n })\n\n test('cache stats 가 합리적 값 반환', async () => {\n const before = getLabelCacheStats()\n expect(before.cacheSize).toBe(0)\n\n await labelcommand({ id: 'lbl1', data: { x: 1 }, context: ctx })\n await labelcommand({ id: 'lbl2', data: { x: 1 }, context: ctx })\n\n const after = getLabelCacheStats()\n expect(after.cacheSize).toBe(2)\n expect(after.inFlight).toBe(0) // 완료 후\n })\n\n test('in-flight 동안 stats.inFlight 증가', async () => {\n buildDelay = 100\n\n const promise = labelcommand({ id: 'lbl1', data: {}, context: ctx })\n // 시작 직후 — buildDelay 안에\n await new Promise(r => setImmediate(r))\n const during = getLabelCacheStats()\n expect(during.inFlight).toBeGreaterThanOrEqual(1)\n\n await promise\n const after = getLabelCacheStats()\n expect(after.inFlight).toBe(0)\n })\n\n test('성공 캐시 후 동일 요청 100 회 동시 → cache hit 만 (build 0 회 추가)', async () => {\n // 첫 호출로 캐시 채움\n await labelcommand({ id: 'lbl1', data: {}, context: ctx })\n expect(buildCalls).toHaveLength(1)\n\n // 동시 100 회 — 모두 cache hit\n const results = await Promise.all(\n Array.from({ length: 100 }, () => labelcommand({ id: 'lbl1', data: {}, context: ctx }))\n )\n expect(buildCalls).toHaveLength(1)\n expect(new Set(results).size).toBe(1)\n })\n})\n\n// ─── 캐시가 테넌트를 넘지 않는다 (2026-09-22) ────────────────────────────────\n\ndescribe('label cache is scoped to who asked', () => {\n const asDomain = (id: string, parentId?: string) => ({ state: { domain: { id, parentId }, user: { id: 'u1' } } })\n\n test('같은 보드 id 를 다른 테넌트가 물으면 키가 다르다', () => {\n const a = cacheKey({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a') })\n const b = cacheKey({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-b') })\n\n expect(a).not.toBe(b)\n })\n\n test('같은 부모를 둔 자식 둘도 서로의 라벨을 받지 않는다', () => {\n const a = cacheKey({ id: 'board-1', data: { x: 1 }, context: asDomain('child-a', 'parent') })\n const b = cacheKey({ id: 'board-1', data: { x: 1 }, context: asDomain('child-b', 'parent') })\n\n expect(a).not.toBe(b)\n })\n\n test('같은 테넌트의 같은 요청은 그대로 한 키다 — 캐시가 죽지 않았다', () => {\n const a = cacheKey({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a', 'parent') })\n const b = cacheKey({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a', 'parent') })\n\n expect(a).toBe(b)\n })\n\n test('캐시를 지난 답이 다른 테넌트에게 안 간다', async () => {\n _resetLabelState()\n\n let built = 0\n _setBuildOverride(async opts => {\n built++\n return `^XA label for ${opts.context.state.domain.id} ^XZ`\n })\n\n try {\n const first = await labelcommand({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-a') })\n const second = await labelcommand({ id: 'board-1', data: { x: 1 }, context: asDomain('tenant-b') })\n\n expect(first).toBe('^XA label for tenant-a ^XZ')\n expect(second).toBe('^XA label for tenant-b ^XZ')\n expect(built).toBe(2)\n } finally {\n _setBuildOverride(null)\n }\n })\n})\n\n// ─── 32 비트 hash 는 다른 라벨을 인쇄한다 (2026-09-22) ───────────────────────\n\ndescribe('the data fingerprint does not collide into another label', () => {\n /*\n * 지어낸 값이 아니다. 짧은 SKU 두 개를 전수로 훑어 찾은 실제 fnv1a-32 충돌이다.\n * 캐시 키가 이 hash 를 쓰던 동안, 둘 중 먼저 인쇄한 라벨이 나머지 하나에도 나갔다.\n */\n const COLLIDING_A = { sku: '967533' }\n const COLLIDING_B = { sku: '1488000' }\n\n test('그 둘이 정말 fnv1a 에서 같은 값이다', () => {\n expect(fnv1a(JSON.stringify(COLLIDING_A))).toBe(fnv1a(JSON.stringify(COLLIDING_B)))\n })\n\n test('그런데도 캐시 키는 갈린다', () => {\n const ctx2 = { state: { domain: { id: 'tenant-a' }, user: { id: 'u1' } } }\n const a = cacheKey({ id: 'board-1', data: COLLIDING_A, context: ctx2 })\n const b = cacheKey({ id: 'board-1', data: COLLIDING_B, context: ctx2 })\n\n expect(a).not.toBe(b)\n })\n})\n"]}
|
|
@@ -228,6 +228,12 @@ exports.standaloneBoardServiceRouter.get('/board/pdf/:id', async (context, next)
|
|
|
228
228
|
// for webpage scrap => zpl image print(grf format) command for released version board
|
|
229
229
|
exports.standaloneBoardServiceRouter.get('/label-command/:id', async (context, next) => {
|
|
230
230
|
const { id } = context.params;
|
|
231
|
+
const { domain, user } = context.state;
|
|
232
|
+
if (!(await auth_base_1.User.hasPrivilege('query', 'board', domain, user))) {
|
|
233
|
+
context.status = 403;
|
|
234
|
+
context.body = { message: 'Access denied' };
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
231
237
|
const data = context.query;
|
|
232
238
|
delete data.access_token;
|
|
233
239
|
const orientation = data && data.orientation;
|
|
@@ -239,6 +245,12 @@ exports.standaloneBoardServiceRouter.get('/label-command/:id', async (context, n
|
|
|
239
245
|
// for webpage scrap => zpl image print(grf format) command for draft version board
|
|
240
246
|
exports.standaloneBoardServiceRouter.get('/label-command-draft/:id', async (context, next) => {
|
|
241
247
|
const { id } = context.params;
|
|
248
|
+
const { domain, user } = context.state;
|
|
249
|
+
if (!(await auth_base_1.User.hasPrivilege('query', 'board', domain, user))) {
|
|
250
|
+
context.status = 403;
|
|
251
|
+
context.body = { message: 'Access denied' };
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
242
254
|
const data = context.query;
|
|
243
255
|
delete data.access_token;
|
|
244
256
|
const orientation = data && data.orientation;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone-board-service-router.js","sourceRoot":"","sources":["../../server/routers/standalone-board-service-router.ts"],"names":[],"mappings":"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","sourcesContent":["import contentDisposition from 'content-disposition'\nimport Router from 'koa-router'\n\nimport { User, setAccessTokenCookie } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { fonts } from '../controllers/fonts.js'\nimport { headlessModel } from '../controllers/headless-model.js'\nimport { headlessPlaylist } from '../controllers/headless-playlist.js'\nimport { labelcommand } from '../controllers/label-command.js'\nimport { pdf } from '../controllers/pdf.js'\nimport { screenshot } from '../controllers/screenshot.js'\nimport { Board } from '../service/board/board.js'\nimport { BoardTemplate } from '../service/board-template/board-template.js'\n\nexport const standaloneBoardServiceRouter = new Router()\n\nfunction parseQuery(query) {\n for (const key in query) {\n if (query.hasOwnProperty(key)) {\n try {\n query[key] = JSON.parse(query[key])\n } catch (error) {\n // do nothing\n }\n }\n }\n\n return query\n}\n\n// for board headless-full\nstandaloneBoardServiceRouter.get('/headless-full/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, base } = await headlessModel({ domain, id })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-full-feature-view', { id, model, data, base })\n})\n\n// for board headless-full-by-name\nstandaloneBoardServiceRouter.get('/headless-full-by-name/:name', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { name } = context.params\n\n const { model, base, board } = await headlessModel({ domain, name })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-full-feature-view', { id: board?.id, model, data, base })\n})\n\n// for board headless\nstandaloneBoardServiceRouter.get('/headless/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, base } = await headlessModel({ domain, id })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-service-view', { id, model, data, base })\n})\n\n// for board headless by name\nstandaloneBoardServiceRouter.get('/headless-by-name/:name', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { name } = context.params\n\n const { model, base, board } = await headlessModel({ domain, name })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-service-view', { id: board?.id, model, data, base })\n})\n\n// for board player\nstandaloneBoardServiceRouter.get('/headless-player/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { playGroup, base } = await headlessPlaylist({ domain, id })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-player-view', {\n id,\n model: {\n boards: playGroup?.boards,\n fonts: fontsToUse,\n fontStyles: fontStyles\n },\n data,\n base\n })\n})\n\n// for board player by name\nstandaloneBoardServiceRouter.get('/headless-player-by-name/:name', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { name } = context.params\n\n const { playGroup, base } = await headlessPlaylist({ domain, name })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-player-view', {\n id: playGroup?.id,\n model: {\n boards: playGroup?.boards,\n fonts: fontsToUse,\n fontStyles: fontStyles\n },\n data,\n base\n })\n})\n\n// for board thumbnail\nstandaloneBoardServiceRouter.get('/thumbnail/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { name, thumbnail } = (await getRepository(Board).findOneBy({ domain: { id: domain.id }, id })) || {}\n\n if (thumbnail) {\n const index = thumbnail.indexOf(';base64,')\n const base64 = thumbnail.substring(index + 8)\n const buffer = Buffer.from(base64, 'base64')\n\n context.type = 'image/png'\n context.set('Content-Disposition', contentDisposition(`thumbnail-${name}.png`))\n context.body = buffer\n } else {\n context.throw(404, 'thumbnail not found')\n }\n})\n\n// for board-template thumbnail\nstandaloneBoardServiceRouter.get('/board-template-thumbnail/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board-template', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { name, thumbnail } = (await getRepository(BoardTemplate).findOneBy({ domain: { id: domain.id }, id })) || {}\n\n if (thumbnail) {\n const index = thumbnail.indexOf(';base64,')\n const base64 = thumbnail.substring(index + 8)\n const buffer = Buffer.from(base64, 'base64')\n\n context.type = 'image/png'\n context.set('Content-Disposition', contentDisposition(`thumbnail-${name}.png`))\n context.body = buffer\n } else {\n context.throw(404, 'thumbnail not found')\n }\n})\n\n// for webpage scrap\nstandaloneBoardServiceRouter.get('/screenshot/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, board } = await headlessModel({ domain, id })\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n context.type = 'image/png'\n context.set('Content-Disposition', contentDisposition(`${board.name}.png`))\n context.body = await screenshot({ id, model, data, options: { encoding: 'binary', type: 'png' }, context })\n})\n\n// for webpage scrap\nstandaloneBoardServiceRouter.get('/board/pdf/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, board } = await headlessModel({ domain, id })\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n const result = await pdf({\n id,\n model,\n data,\n options: {\n format: 'A4'\n },\n context\n })\n\n context.type = 'application/pdf'\n context.set('Content-Disposition', contentDisposition(`${board.name}.pdf`))\n context.body = result\n})\n\n// for webpage scrap => zpl image print(grf format) command for released version board\nstandaloneBoardServiceRouter.get('/label-command/:id', async (context, next) => {\n const { id } = context.params\n\n const data = context.query\n delete data.access_token\n\n const orientation = data && data.orientation\n const mirror = data && data.mirror\n const upsideDown = data && data.upsideDown\n\n context.type = 'text/plain'\n context.body = await labelcommand({ id, data, orientation, mirror, upsideDown, context } as any)\n})\n\n// for webpage scrap => zpl image print(grf format) command for draft version board\nstandaloneBoardServiceRouter.get('/label-command-draft/:id', async (context, next) => {\n const { id } = context.params\n\n const data = context.query\n delete data.access_token\n\n const orientation = data && data.orientation\n const mirror = data && data.mirror\n const upsideDown = data && data.upsideDown\n\n context.type = 'text/plain'\n context.body = await labelcommand({ id, data, orientation, mirror, upsideDown, context, draft: true } as any)\n})\n"]}
|
|
1
|
+
{"version":3,"file":"standalone-board-service-router.js","sourceRoot":"","sources":["../../server/routers/standalone-board-service-router.ts"],"names":[],"mappings":"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","sourcesContent":["import contentDisposition from 'content-disposition'\nimport Router from 'koa-router'\n\nimport { User, setAccessTokenCookie } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { fonts } from '../controllers/fonts.js'\nimport { headlessModel } from '../controllers/headless-model.js'\nimport { headlessPlaylist } from '../controllers/headless-playlist.js'\nimport { labelcommand } from '../controllers/label-command.js'\nimport { pdf } from '../controllers/pdf.js'\nimport { screenshot } from '../controllers/screenshot.js'\nimport { Board } from '../service/board/board.js'\nimport { BoardTemplate } from '../service/board-template/board-template.js'\n\nexport const standaloneBoardServiceRouter = new Router()\n\nfunction parseQuery(query) {\n for (const key in query) {\n if (query.hasOwnProperty(key)) {\n try {\n query[key] = JSON.parse(query[key])\n } catch (error) {\n // do nothing\n }\n }\n }\n\n return query\n}\n\n// for board headless-full\nstandaloneBoardServiceRouter.get('/headless-full/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, base } = await headlessModel({ domain, id })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-full-feature-view', { id, model, data, base })\n})\n\n// for board headless-full-by-name\nstandaloneBoardServiceRouter.get('/headless-full-by-name/:name', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { name } = context.params\n\n const { model, base, board } = await headlessModel({ domain, name })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-full-feature-view', { id: board?.id, model, data, base })\n})\n\n// for board headless\nstandaloneBoardServiceRouter.get('/headless/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, base } = await headlessModel({ domain, id })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-service-view', { id, model, data, base })\n})\n\n// for board headless by name\nstandaloneBoardServiceRouter.get('/headless-by-name/:name', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { name } = context.params\n\n const { model, base, board } = await headlessModel({ domain, name })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-service-view', { id: board?.id, model, data, base })\n})\n\n// for board player\nstandaloneBoardServiceRouter.get('/headless-player/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { playGroup, base } = await headlessPlaylist({ domain, id })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-player-view', {\n id,\n model: {\n boards: playGroup?.boards,\n fonts: fontsToUse,\n fontStyles: fontStyles\n },\n data,\n base\n })\n})\n\n// for board player by name\nstandaloneBoardServiceRouter.get('/headless-player-by-name/:name', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { name } = context.params\n\n const { playGroup, base } = await headlessPlaylist({ domain, name })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n setAccessTokenCookie(context, await user.sign({ domain }))\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n await context.render('internal-board-player-view', {\n id: playGroup?.id,\n model: {\n boards: playGroup?.boards,\n fonts: fontsToUse,\n fontStyles: fontStyles\n },\n data,\n base\n })\n})\n\n// for board thumbnail\nstandaloneBoardServiceRouter.get('/thumbnail/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { name, thumbnail } = (await getRepository(Board).findOneBy({ domain: { id: domain.id }, id })) || {}\n\n if (thumbnail) {\n const index = thumbnail.indexOf(';base64,')\n const base64 = thumbnail.substring(index + 8)\n const buffer = Buffer.from(base64, 'base64')\n\n context.type = 'image/png'\n context.set('Content-Disposition', contentDisposition(`thumbnail-${name}.png`))\n context.body = buffer\n } else {\n context.throw(404, 'thumbnail not found')\n }\n})\n\n// for board-template thumbnail\nstandaloneBoardServiceRouter.get('/board-template-thumbnail/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board-template', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { name, thumbnail } = (await getRepository(BoardTemplate).findOneBy({ domain: { id: domain.id }, id })) || {}\n\n if (thumbnail) {\n const index = thumbnail.indexOf(';base64,')\n const base64 = thumbnail.substring(index + 8)\n const buffer = Buffer.from(base64, 'base64')\n\n context.type = 'image/png'\n context.set('Content-Disposition', contentDisposition(`thumbnail-${name}.png`))\n context.body = buffer\n } else {\n context.throw(404, 'thumbnail not found')\n }\n})\n\n// for webpage scrap\nstandaloneBoardServiceRouter.get('/screenshot/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, board } = await headlessModel({ domain, id })\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n context.type = 'image/png'\n context.set('Content-Disposition', contentDisposition(`${board.name}.png`))\n context.body = await screenshot({ id, model, data, options: { encoding: 'binary', type: 'png' }, context })\n})\n\n// for webpage scrap\nstandaloneBoardServiceRouter.get('/board/pdf/:id', async (context, next) => {\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n return\n }\n\n const { id } = context.params\n\n const { model, board } = await headlessModel({ domain, id })\n\n const data = parseQuery({ ...context.query })\n delete data.access_token\n\n const result = await pdf({\n id,\n model,\n data,\n options: {\n format: 'A4'\n },\n context\n })\n\n context.type = 'application/pdf'\n context.set('Content-Disposition', contentDisposition(`${board.name}.pdf`))\n context.body = result\n})\n\n// for webpage scrap => zpl image print(grf format) command for released version board\nstandaloneBoardServiceRouter.get('/label-command/:id', async (context, next) => {\n const { id } = context.params\n\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n context.body = { message: 'Access denied' }\n return\n }\n\n const data = context.query\n delete data.access_token\n\n const orientation = data && data.orientation\n const mirror = data && data.mirror\n const upsideDown = data && data.upsideDown\n\n context.type = 'text/plain'\n context.body = await labelcommand({ id, data, orientation, mirror, upsideDown, context } as any)\n})\n\n// for webpage scrap => zpl image print(grf format) command for draft version board\nstandaloneBoardServiceRouter.get('/label-command-draft/:id', async (context, next) => {\n const { id } = context.params\n\n const { domain, user } = context.state\n\n if (!(await User.hasPrivilege('query', 'board', domain, user))) {\n context.status = 403\n context.body = { message: 'Access denied' }\n return\n }\n\n const data = context.query\n delete data.access_token\n\n const orientation = data && data.orientation\n const mirror = data && data.mirror\n const upsideDown = data && data.upsideDown\n\n context.type = 'text/plain'\n context.body = await labelcommand({ id, data, orientation, mirror, upsideDown, context, draft: true } as any)\n})\n"]}
|