@things-factory/shell 10.1.154 → 10.1.155

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ import { Domain } from '../service/domain/domain.js';
2
+ /** The domain's id followed by its ancestors' ids, closest first. */
3
+ export declare function getDomainIdsWithAncestors(domain: Domain): Promise<string[]>;
4
+ /**
5
+ * Keeps one item per key - the one declared by the closest domain - in the order the keys first
6
+ * appear. `domainIds` is the closest-first list from getDomainIdsWithAncestors.
7
+ */
8
+ export declare function dedupByClosestDomain<T extends {
9
+ domainId?: string;
10
+ }>(items: T[], domainIds: string[], keyFn: (item: T) => string | undefined): T[];
11
+ /** The active EnvVar of that name declared by the closest domain, or undefined. */
12
+ export declare function lookupDomainEnvVar(domain: Domain, name: string): Promise<string | undefined>;
13
+ /** Like lookupDomainEnvVar, and also says which domain the value came from. */
14
+ export declare function resolveDomainEnvVar(domain: Domain, name: string): Promise<{
15
+ value: string;
16
+ domainId: string;
17
+ updatedAt?: Date;
18
+ } | undefined>;
19
+ /**
20
+ * Every active EnvVar visible from the domain, merged closest-wins into one record. With a prefix,
21
+ * only the names that start with it.
22
+ */
23
+ export declare function loadDomainAttributes(domain: Domain, prefix?: string): Promise<Record<string, string>>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDomainIdsWithAncestors = getDomainIdsWithAncestors;
4
+ exports.dedupByClosestDomain = dedupByClosestDomain;
5
+ exports.lookupDomainEnvVar = lookupDomainEnvVar;
6
+ exports.resolveDomainEnvVar = resolveDomainEnvVar;
7
+ exports.loadDomainAttributes = loadDomainAttributes;
8
+ const typeorm_1 = require("typeorm");
9
+ const database_js_1 = require("../initializers/database.js");
10
+ const domain_js_1 = require("../service/domain/domain.js");
11
+ const env_var_js_1 = require("../service/env-var/env-var.js");
12
+ /**
13
+ * Multi-level domain inheritance — the one place that walks a domain's ancestors.
14
+ *
15
+ * A child domain inherits what its ancestors declare, and the closest declaration wins.
16
+ * Callers use the ids for `domain IN (:...ids)` list queries, for picking the closest row of a
17
+ * single fetch, or for walking a fallback chain in order.
18
+ *
19
+ * Guarded against cycles and capped at a depth of 10.
20
+ */
21
+ const MAX_ANCESTOR_DEPTH = 10;
22
+ /** The domain's id followed by its ancestors' ids, closest first. */
23
+ async function getDomainIdsWithAncestors(domain) {
24
+ const ids = [domain.id];
25
+ const seen = new Set([domain.id]);
26
+ let parentId = domain.parentId;
27
+ let depth = 0;
28
+ while (parentId && !seen.has(parentId) && depth < MAX_ANCESTOR_DEPTH) {
29
+ seen.add(parentId);
30
+ ids.push(parentId);
31
+ const parent = await (0, database_js_1.getRepository)(domain_js_1.Domain).findOne({ where: { id: parentId } });
32
+ parentId = parent?.parentId;
33
+ depth++;
34
+ }
35
+ return ids;
36
+ }
37
+ /**
38
+ * Keeps one item per key - the one declared by the closest domain - in the order the keys first
39
+ * appear. `domainIds` is the closest-first list from getDomainIdsWithAncestors.
40
+ */
41
+ function dedupByClosestDomain(items, domainIds, keyFn) {
42
+ const rankOf = (domainId) => {
43
+ if (!domainId)
44
+ return Number.MAX_SAFE_INTEGER;
45
+ const idx = domainIds.indexOf(domainId);
46
+ return idx < 0 ? Number.MAX_SAFE_INTEGER : idx;
47
+ };
48
+ const byKey = new Map();
49
+ const order = [];
50
+ for (const item of items) {
51
+ const key = keyFn(item);
52
+ if (key == null)
53
+ continue;
54
+ const existing = byKey.get(key);
55
+ if (!existing) {
56
+ byKey.set(key, item);
57
+ order.push(key);
58
+ }
59
+ else if (rankOf(item.domainId) < rankOf(existing.domainId)) {
60
+ byKey.set(key, item);
61
+ }
62
+ }
63
+ return order.map(k => byKey.get(k));
64
+ }
65
+ /** The active EnvVar of that name declared by the closest domain, or undefined. */
66
+ async function lookupDomainEnvVar(domain, name) {
67
+ return (await resolveDomainEnvVar(domain, name))?.value;
68
+ }
69
+ /** Like lookupDomainEnvVar, and also says which domain the value came from. */
70
+ async function resolveDomainEnvVar(domain, name) {
71
+ if (!domain)
72
+ return undefined;
73
+ const ids = await getDomainIdsWithAncestors(domain);
74
+ const candidates = await (0, database_js_1.getRepository)(env_var_js_1.EnvVar).find({
75
+ where: { domain: { id: (0, typeorm_1.In)(ids) }, name, active: true }
76
+ });
77
+ const [closest] = dedupByClosestDomain(candidates, ids, () => name);
78
+ return closest ? { value: closest.value, domainId: closest.domainId, updatedAt: closest.updatedAt } : undefined;
79
+ }
80
+ /**
81
+ * Every active EnvVar visible from the domain, merged closest-wins into one record. With a prefix,
82
+ * only the names that start with it.
83
+ */
84
+ async function loadDomainAttributes(domain, prefix) {
85
+ if (!domain)
86
+ return {};
87
+ const ids = await getDomainIdsWithAncestors(domain);
88
+ const all = await (0, database_js_1.getRepository)(env_var_js_1.EnvVar).find({
89
+ where: { domain: { id: (0, typeorm_1.In)(ids) }, active: true }
90
+ });
91
+ const visible = all.filter(e => !prefix || e.name.startsWith(prefix));
92
+ const result = {};
93
+ for (const e of dedupByClosestDomain(visible, ids, e => e.name))
94
+ result[e.name] = e.value;
95
+ return result;
96
+ }
97
+ //# sourceMappingURL=domain-ancestry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-ancestry.js","sourceRoot":"","sources":["../../server/utils/domain-ancestry.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { getRepository } from '../initializers/database.js'\nimport { Domain } from '../service/domain/domain.js'\nimport { EnvVar } from '../service/env-var/env-var.js'\n\n/**\n * Multi-level domain inheritance — the one place that walks a domain's ancestors.\n *\n * A child domain inherits what its ancestors declare, and the closest declaration wins.\n * Callers use the ids for `domain IN (:...ids)` list queries, for picking the closest row of a\n * single fetch, or for walking a fallback chain in order.\n *\n * Guarded against cycles and capped at a depth of 10.\n */\nconst MAX_ANCESTOR_DEPTH = 10\n\n/** The domain's id followed by its ancestors' ids, closest first. */\nexport async function getDomainIdsWithAncestors(domain: Domain): Promise<string[]> {\n const ids: string[] = [domain.id]\n const seen = new Set<string>([domain.id])\n\n let parentId: string | undefined = domain.parentId\n let depth = 0\n\n while (parentId && !seen.has(parentId) && depth < MAX_ANCESTOR_DEPTH) {\n seen.add(parentId)\n ids.push(parentId)\n\n const parent = await getRepository(Domain).findOne({ where: { id: parentId } })\n parentId = parent?.parentId\n depth++\n }\n\n return ids\n}\n\n/**\n * Keeps one item per key - the one declared by the closest domain - in the order the keys first\n * appear. `domainIds` is the closest-first list from getDomainIdsWithAncestors.\n */\nexport function dedupByClosestDomain<T extends { domainId?: string }>(\n items: T[],\n domainIds: string[],\n keyFn: (item: T) => string | undefined\n): T[] {\n const rankOf = (domainId: string | undefined): number => {\n if (!domainId) return Number.MAX_SAFE_INTEGER\n const idx = domainIds.indexOf(domainId)\n return idx < 0 ? Number.MAX_SAFE_INTEGER : idx\n }\n\n const byKey = new Map<string, T>()\n const order: string[] = []\n\n for (const item of items) {\n const key = keyFn(item)\n if (key == null) continue\n\n const existing = byKey.get(key)\n if (!existing) {\n byKey.set(key, item)\n order.push(key)\n } else if (rankOf(item.domainId) < rankOf(existing.domainId)) {\n byKey.set(key, item)\n }\n }\n\n return order.map(k => byKey.get(k)!)\n}\n\n/** The active EnvVar of that name declared by the closest domain, or undefined. */\nexport async function lookupDomainEnvVar(domain: Domain, name: string): Promise<string | undefined> {\n return (await resolveDomainEnvVar(domain, name))?.value\n}\n\n/** Like lookupDomainEnvVar, and also says which domain the value came from. */\nexport async function resolveDomainEnvVar(\n domain: Domain,\n name: string\n): Promise<{ value: string; domainId: string; updatedAt?: Date } | undefined> {\n if (!domain) return undefined\n const ids = await getDomainIdsWithAncestors(domain)\n const candidates = await getRepository(EnvVar).find({\n where: { domain: { id: In(ids) }, name, active: true }\n })\n const [closest] = dedupByClosestDomain(candidates, ids, () => name)\n return closest ? { value: closest.value, domainId: closest.domainId, updatedAt: closest.updatedAt } : undefined\n}\n\n/**\n * Every active EnvVar visible from the domain, merged closest-wins into one record. With a prefix,\n * only the names that start with it.\n */\nexport async function loadDomainAttributes(domain: Domain, prefix?: string): Promise<Record<string, string>> {\n if (!domain) return {}\n const ids = await getDomainIdsWithAncestors(domain)\n const all = await getRepository(EnvVar).find({\n where: { domain: { id: In(ids) }, active: true }\n })\n const visible = all.filter(e => !prefix || e.name.startsWith(prefix))\n const result: Record<string, string> = {}\n for (const e of dedupByClosestDomain(visible, ids, e => e.name)) result[e.name] = e.value\n return result\n}\n"]}
@@ -11,3 +11,5 @@ export * from './get-times-for-period.js';
11
11
  export * from './headless-pool/index.js';
12
12
  export * from './subscribe-auth.js';
13
13
  export * from './after-commit.js';
14
+ export * from './domain-ancestry.js';
15
+ export * from './secret-hint.js';
@@ -14,4 +14,6 @@ tslib_1.__exportStar(require("./get-times-for-period.js"), exports);
14
14
  tslib_1.__exportStar(require("./headless-pool/index.js"), exports);
15
15
  tslib_1.__exportStar(require("./subscribe-auth.js"), exports);
16
16
  tslib_1.__exportStar(require("./after-commit.js"), exports);
17
+ tslib_1.__exportStar(require("./domain-ancestry.js"), exports);
18
+ tslib_1.__exportStar(require("./secret-hint.js"), exports);
17
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/utils/index.ts"],"names":[],"mappings":";;;AAAA,0DAA+B;AAC/B,mEAAwC;AACxC,iEAAsC;AACtC,kEAAuC;AACvC,qEAA0C;AAC1C,gEAAqC;AACrC,kFAAuD;AACvD,6DAAkC;AAClC,mEAAwC;AACxC,oEAAyC;AACzC,mEAAwC;AACxC,8DAAmC;AACnC,4DAAiC","sourcesContent":["export * from './get-domain.js'\nexport * from './apply-domain-filter.js'\nexport * from './condition-builder.js'\nexport * from './list-query-builder.js'\nexport * from './list-params-converter.js'\nexport * from './publish-progress.js'\nexport * from './get-query-builder-from-list-params.js'\nexport * from './keyset-cursor.js'\nexport * from './list-param-adjuster.js'\nexport * from './get-times-for-period.js'\nexport * from './headless-pool/index.js'\nexport * from './subscribe-auth.js'\nexport * from './after-commit.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/utils/index.ts"],"names":[],"mappings":";;;AAAA,0DAA+B;AAC/B,mEAAwC;AACxC,iEAAsC;AACtC,kEAAuC;AACvC,qEAA0C;AAC1C,gEAAqC;AACrC,kFAAuD;AACvD,6DAAkC;AAClC,mEAAwC;AACxC,oEAAyC;AACzC,mEAAwC;AACxC,8DAAmC;AACnC,4DAAiC;AACjC,+DAAoC;AACpC,2DAAgC","sourcesContent":["export * from './get-domain.js'\nexport * from './apply-domain-filter.js'\nexport * from './condition-builder.js'\nexport * from './list-query-builder.js'\nexport * from './list-params-converter.js'\nexport * from './publish-progress.js'\nexport * from './get-query-builder-from-list-params.js'\nexport * from './keyset-cursor.js'\nexport * from './list-param-adjuster.js'\nexport * from './get-times-for-period.js'\nexport * from './headless-pool/index.js'\nexport * from './subscribe-auth.js'\nexport * from './after-commit.js'\nexport * from './domain-ancestry.js'\nexport * from './secret-hint.js'\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * What of a withheld value may be shown, so that a person can tell which one is set (2026-10-04,
3
+ * user decision on ADR-0112):
4
+ *
5
+ * 16 characters or more the last four ••••xyze
6
+ * 4 to 15 the first and the last a••••e
7
+ * fewer than 4 nothing ••••
8
+ *
9
+ * The middle is never shown, and the mask is always four dots so the length does not show either.
10
+ * Made here from the stored value - the value itself never leaves the server.
11
+ */
12
+ export declare function secretHint(value: string | null | undefined): string | undefined;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretHint = secretHint;
4
+ /**
5
+ * What of a withheld value may be shown, so that a person can tell which one is set (2026-10-04,
6
+ * user decision on ADR-0112):
7
+ *
8
+ * 16 characters or more the last four ••••xyze
9
+ * 4 to 15 the first and the last a••••e
10
+ * fewer than 4 nothing ••••
11
+ *
12
+ * The middle is never shown, and the mask is always four dots so the length does not show either.
13
+ * Made here from the stored value - the value itself never leaves the server.
14
+ */
15
+ function secretHint(value) {
16
+ if (value === null || value === undefined || value === '') {
17
+ return undefined;
18
+ }
19
+ const characters = Array.from(String(value));
20
+ const MASK = '••••';
21
+ if (characters.length >= 16) {
22
+ return MASK + characters.slice(-4).join('');
23
+ }
24
+ if (characters.length >= 4) {
25
+ return characters[0] + MASK + characters[characters.length - 1];
26
+ }
27
+ return MASK;
28
+ }
29
+ //# sourceMappingURL=secret-hint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-hint.js","sourceRoot":"","sources":["../../server/utils/secret-hint.ts"],"names":[],"mappings":";;AAWA,gCAeC;AA1BD;;;;;;;;;;GAUG;AACH,SAAgB,UAAU,CAAC,KAAgC;IACzD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IAC5C,MAAM,IAAI,GAAG,MAAM,CAAA;IAEnB,IAAI,UAAU,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QAC5B,OAAO,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC7C,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * What of a withheld value may be shown, so that a person can tell which one is set (2026-10-04,\n * user decision on ADR-0112):\n *\n * 16 characters or more the last four ••••xyze\n * 4 to 15 the first and the last a••••e\n * fewer than 4 nothing ••••\n *\n * The middle is never shown, and the mask is always four dots so the length does not show either.\n * Made here from the stored value - the value itself never leaves the server.\n */\nexport function secretHint(value: string | null | undefined): string | undefined {\n if (value === null || value === undefined || value === '') {\n return undefined\n }\n\n const characters = Array.from(String(value))\n const MASK = '••••'\n\n if (characters.length >= 16) {\n return MASK + characters.slice(-4).join('')\n }\n if (characters.length >= 4) {\n return characters[0] + MASK + characters[characters.length - 1]\n }\n return MASK\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/shell",
3
- "version": "10.1.154",
3
+ "version": "10.1.155",
4
4
  "description": "Core module for framework",
5
5
  "bin": {
6
6
  "things-factory": "bin/things-factory",
@@ -142,5 +142,5 @@
142
142
  "pg": "^8.7.3",
143
143
  "sqlite3": "^5.0.8"
144
144
  },
145
- "gitHead": "f28579aa7131cbb282eb8813f8b285d9d29bb525"
145
+ "gitHead": "0211c9c0764a26c10151fdc7d60094fc334905ff"
146
146
  }
@@ -183,5 +183,7 @@
183
183
  "text.unsaved-draft-kept": "what you changed {minutes} minutes ago is still kept in this browser.",
184
184
  "text.unsaved-draft-behind": "it has been saved again since then. saving will tell you about the conflict.",
185
185
  "button.resume-editing": "resume editing",
186
- "button.discard": "discard"
186
+ "button.discard": "discard",
187
+ "figure.label.motion": "Motion",
188
+ "figure.text.preview-needs-a-part": "add a part to see how it looks placed on a drawing."
187
189
  }
@@ -183,5 +183,7 @@
183
183
  "text.unsaved-draft-kept": "los cambios que hizo hace {minutes} minutos siguen guardados en este navegador.",
184
184
  "text.unsaved-draft-behind": "desde entonces se ha vuelto a guardar. al guardar se le avisará del conflicto.",
185
185
  "button.resume-editing": "reanudar edición",
186
- "button.discard": "descartar"
186
+ "button.discard": "descartar",
187
+ "figure.label.motion": "Movimiento",
188
+ "figure.text.preview-needs-a-part": "añada una pieza para ver cómo se ve colocada en un plano."
187
189
  }
@@ -183,5 +183,7 @@
183
183
  "text.unsaved-draft-kept": "{minutes}分前に変更した内容がこのブラウザに残っています。",
184
184
  "text.unsaved-draft-behind": "その後、再び保存されています。保存するときに競合をお知らせします。",
185
185
  "button.resume-editing": "編集を続ける",
186
- "button.discard": "破棄"
186
+ "button.discard": "破棄",
187
+ "figure.label.motion": "Motion",
188
+ "figure.text.preview-needs-a-part": "部品を追加すると、図面に置いたときの姿が表示されます。"
187
189
  }
@@ -183,5 +183,7 @@
183
183
  "text.unsaved-draft-kept": "{minutes}분 전에 고친 내용이 이 브라우저에 남아 있습니다.",
184
184
  "text.unsaved-draft-behind": "그 뒤에 다시 저장되었습니다. 저장할 때 충돌을 알려 드립니다.",
185
185
  "button.resume-editing": "이어서 편집",
186
- "button.discard": "버리기"
186
+ "button.discard": "버리기",
187
+ "figure.label.motion": "움직임",
188
+ "figure.text.preview-needs-a-part": "부품을 추가하면 도면에 놓았을 때의 모습이 표시됩니다."
187
189
  }
@@ -183,5 +183,7 @@
183
183
  "text.unsaved-draft-kept": "perubahan yang anda buat {minutes} minit lalu masih disimpan dalam pelayar ini.",
184
184
  "text.unsaved-draft-behind": "ia telah disimpan semula sejak itu. semasa menyimpan, anda akan dimaklumkan tentang konflik.",
185
185
  "button.resume-editing": "sambung menyunting",
186
- "button.discard": "buang"
186
+ "button.discard": "buang",
187
+ "figure.label.motion": "Motion",
188
+ "figure.text.preview-needs-a-part": "tambah bahagian untuk melihat rupanya apabila diletakkan pada lukisan."
187
189
  }
@@ -183,5 +183,7 @@
183
183
  "text.unsaved-draft-kept": "您 {minutes} 分钟前所做的更改仍保留在此浏览器中。",
184
184
  "text.unsaved-draft-behind": "此后已被再次保存。保存时会提示冲突。",
185
185
  "button.resume-editing": "继续编辑",
186
- "button.discard": "放弃"
186
+ "button.discard": "放弃",
187
+ "figure.label.motion": "Motion",
188
+ "figure.text.preview-needs-a-part": "添加部件后会显示放置到图面上的效果。"
187
189
  }