@things-factory/shell 10.1.29 → 10.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  /*
2
- * Accent-derived slots read `--brand-<slot>` so a product can set them from its own brand.css.
2
+ * Accent-derived slots read `--brand-<slot>` so a product can set them from its own accent theme
3
+ * file, named after the product (`plant-accent.css`) - never after a shell theme file.
3
4
  * Neutral slots read `--suite-<slot>`, set by the product suite (@things-factory/operato-suite) for the
4
5
  * apps that depend on it. Bundle order does not matter for a var(); the fallback is the shell value.
5
6
  * Error, shadow and scrim stay literal on purpose - nobody above the platform owns them.
@@ -1,5 +1,6 @@
1
1
  /*
2
- * Accent-derived slots read `--brand-<slot>` so a product can set them from its own brand.css.
2
+ * Accent-derived slots read `--brand-<slot>` so a product can set them from its own accent theme
3
+ * file, named after the product (`plant-accent.css`) - never after a shell theme file.
3
4
  * Neutral slots read `--suite-<slot>`, set by the product suite (@things-factory/operato-suite) for the
4
5
  * apps that depend on it. Bundle order does not matter for a var(); the fallback is the shell value.
5
6
  * Error, shadow and scrim stay literal on purpose - nobody above the platform owns them.
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * [넣는 것] 브랜드와 무관하게 항상 성립해야 하는 규칙(테마 스킴, 공통 접근성 기본값 등)
14
14
  * [넣지 않는 것] 브랜드 색값.
15
- * Products set accent colours only, as `--brand-<slot>` in their own `brand.css`. The shell
15
+ * Products set accent colours only, as `--brand-<slot>` in their own `<product>-accent.css`. The shell
16
16
  * `light.css` / `dark.css` read the 26 accent-derived md-sys slots from those variables, the 17
17
17
  * neutral slots from `--suite-<slot>` (the product suite's shared greys), and keep the last 6
18
18
  * (error, shadow, scrim) literal. See tests/md-sys-slot-owners.test.ts.
@@ -8,6 +8,7 @@ export * from './middlewares/index.js';
8
8
  export * from './bootstrap-hooks.js';
9
9
  export * from './graphql-local-client.js';
10
10
  export * from './service/index.js';
11
+ export * from './refusal/index.js';
11
12
  export * from './typeorm/long-text-column.js';
12
13
  export * from './typeorm/encrypt-transform.js';
13
14
  export * from './typeorm/encrypt-json-transform.js';
@@ -11,6 +11,7 @@ tslib_1.__exportStar(require("./middlewares/index.js"), exports);
11
11
  tslib_1.__exportStar(require("./bootstrap-hooks.js"), exports);
12
12
  tslib_1.__exportStar(require("./graphql-local-client.js"), exports);
13
13
  tslib_1.__exportStar(require("./service/index.js"), exports);
14
+ tslib_1.__exportStar(require("./refusal/index.js"), exports);
14
15
  tslib_1.__exportStar(require("./typeorm/long-text-column.js"), exports);
15
16
  tslib_1.__exportStar(require("./typeorm/encrypt-transform.js"), exports);
16
17
  tslib_1.__exportStar(require("./typeorm/encrypt-json-transform.js"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,gEAAqC;AACrC,4EAAiD;AACjD,qEAA0C;AAC1C,2DAAgC;AAChC,sDAA2B;AAC3B,oEAAyC;AACzC,iEAAsC;AACtC,+DAAoC;AACpC,oEAAyC;AACzC,6DAAkC;AAElC,wEAA6C;AAC7C,yEAA8C;AAC9C,8EAAmD;AACnD,uEAA4C;AAC5C,uEAA4C","sourcesContent":["export * from './migrations/index.js'\nexport * from './initializers/naming-strategy.js'\nexport * from './initializers/database.js'\nexport * from './utils/index.js'\nexport * from './pubsub.js'\nexport * from './pubsub-log-transport.js'\nexport * from './middlewares/index.js'\nexport * from './bootstrap-hooks.js'\nexport * from './graphql-local-client.js'\nexport * from './service/index.js'\n\nexport * from './typeorm/long-text-column.js'\nexport * from './typeorm/encrypt-transform.js'\nexport * from './typeorm/encrypt-json-transform.js'\nexport * from './typeorm/json5-transform.js'\nexport * from './typeorm/round-transform.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,gEAAqC;AACrC,4EAAiD;AACjD,qEAA0C;AAC1C,2DAAgC;AAChC,sDAA2B;AAC3B,oEAAyC;AACzC,iEAAsC;AACtC,+DAAoC;AACpC,oEAAyC;AACzC,6DAAkC;AAClC,6DAAkC;AAElC,wEAA6C;AAC7C,yEAA8C;AAC9C,8EAAmD;AACnD,uEAA4C;AAC5C,uEAA4C","sourcesContent":["export * from './migrations/index.js'\nexport * from './initializers/naming-strategy.js'\nexport * from './initializers/database.js'\nexport * from './utils/index.js'\nexport * from './pubsub.js'\nexport * from './pubsub-log-transport.js'\nexport * from './middlewares/index.js'\nexport * from './bootstrap-hooks.js'\nexport * from './graphql-local-client.js'\nexport * from './service/index.js'\nexport * from './refusal/index.js'\n\nexport * from './typeorm/long-text-column.js'\nexport * from './typeorm/encrypt-transform.js'\nexport * from './typeorm/encrypt-json-transform.js'\nexport * from './typeorm/json5-transform.js'\nexport * from './typeorm/round-transform.js'\n"]}
@@ -0,0 +1 @@
1
+ export * from './refusal.js';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./refusal.js"), exports);
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/refusal/index.ts"],"names":[],"mappings":";;;AAAA,uDAA4B","sourcesContent":["export * from './refusal.js'\n"]}
@@ -0,0 +1,70 @@
1
+ import { GraphQLError } from 'graphql';
2
+ import type { MiddlewareFn } from 'type-graphql';
3
+ /**
4
+ * **A refusal the server knows and names** (ADR-0054).
5
+ *
6
+ * ── Why a gate does not throw a sentence ──────────────────────────────────
7
+ * A gate that throws an English sentence puts English on a Korean screen. A screen that translates
8
+ * it is then writing the reason itself, and the gate and the screen end up with two accounts of the
9
+ * same refusal. So the gate throws a name and values, and the sentence is made where the request's
10
+ * language is known.
11
+ *
12
+ * ── What gets stored is the name ──────────────────────────────────────────
13
+ * Never write the sentence onto an event or a work row. A sentence differs per reader and the wording
14
+ * changes later; a stored one freezes that day's language. The `code` is the contract, and it travels
15
+ * unchanged to the client (ADR-0042 - no mapping table on the way out).
16
+ *
17
+ * ── Two traps ─────────────────────────────────────────────────────────────
18
+ * ① `t(key, undefined)` loses the locale and answers in English, so `params` is always passed - the
19
+ * spread below makes an object even when a refusal carries none.
20
+ * ② Never pass a **value** to `t` as a key: `:` reads as a namespace separator and cuts `urn:epc:…`
21
+ * apart. Values travel in `params` only.
22
+ *
23
+ * ── Why it lives in the shell ─────────────────────────────────────────────
24
+ * operato-plant wrote this first and operato-warehouse copied it; worklist is the third consumer as
25
+ * the work-door screens move there (ADR-0069 decision 3). Everything it needs is `graphql`,
26
+ * `type-graphql` and the request's `t`, all of which sit at or below the shell, so this is the floor
27
+ * that every consumer already stands on. The translations stay with each module: `refusalKey` names
28
+ * `error.<code>` inside the caller's own namespace, and a module that does not declare the key falls
29
+ * back to the English sentence rather than printing the key.
30
+ */
31
+ export declare class Refusal extends Error {
32
+ readonly code: string;
33
+ readonly params: RefusalParams;
34
+ /**
35
+ * @param code this refusal's name (kebab). It states **the gate's fact**; a screen does not pick its
36
+ * own sentence from this name.
37
+ * @param params the values the sentence is built from.
38
+ * @param english the sentence used when the translation key is missing. An English sentence reads
39
+ * better than the key printed raw.
40
+ */
41
+ constructor(code: string, params: RefusalParams, english: string);
42
+ }
43
+ /** Values a refusal sentence is built from. Scalars only: they travel in the GraphQL response as they are. */
44
+ export type RefusalParams = Record<string, string | number | boolean | null>;
45
+ export declare function refusalKey(code: string): string;
46
+ type Translate = (key: string, options?: Record<string, unknown>) => string;
47
+ /**
48
+ * **The one place a refusal becomes a sentence.** The resolver wrapper and the worklist callbacks both
49
+ * call this, so the two exits cannot drift apart.
50
+ *
51
+ * Apollo's own `extensions.code` gets `BAD_USER_INPUT`: a refusal is not a server error, and left empty
52
+ * Apollo would fill in `INTERNAL_SERVER_ERROR`. The name rides separately in `extensions.refusal`; its
53
+ * presence is what tells a client the server refused on purpose.
54
+ */
55
+ export declare function speakRefusal(refusal: Refusal, context: {
56
+ t?: Translate;
57
+ }): GraphQLError;
58
+ /** Rethrow a refusal as a sentence in the caller's language; anything else passes through untouched. */
59
+ export declare function rethrowSpoken(error: unknown, context: {
60
+ t?: Translate;
61
+ }): never;
62
+ /** A resolver class passes through this (`@UseMiddleware(SpeakRefusals)`). */
63
+ export declare const SpeakRefusals: MiddlewareFn<any>;
64
+ /**
65
+ * An activity callback runs inside a worklist mutation, not the module's own resolver, so
66
+ * `SpeakRefusals` never sees what it throws. Declarations wrap their callback in this: the same
67
+ * `speakRefusal`, the other exit.
68
+ */
69
+ export declare function speaksRefusals(callback: (instance: any, addendum: any, context: any) => Promise<void>): (instance: any, addendum: any, context: any) => Promise<void>;
70
+ export {};
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpeakRefusals = exports.Refusal = void 0;
4
+ exports.refusalKey = refusalKey;
5
+ exports.speakRefusal = speakRefusal;
6
+ exports.rethrowSpoken = rethrowSpoken;
7
+ exports.speaksRefusals = speaksRefusals;
8
+ const graphql_1 = require("graphql");
9
+ /**
10
+ * **A refusal the server knows and names** (ADR-0054).
11
+ *
12
+ * ── Why a gate does not throw a sentence ──────────────────────────────────
13
+ * A gate that throws an English sentence puts English on a Korean screen. A screen that translates
14
+ * it is then writing the reason itself, and the gate and the screen end up with two accounts of the
15
+ * same refusal. So the gate throws a name and values, and the sentence is made where the request's
16
+ * language is known.
17
+ *
18
+ * ── What gets stored is the name ──────────────────────────────────────────
19
+ * Never write the sentence onto an event or a work row. A sentence differs per reader and the wording
20
+ * changes later; a stored one freezes that day's language. The `code` is the contract, and it travels
21
+ * unchanged to the client (ADR-0042 - no mapping table on the way out).
22
+ *
23
+ * ── Two traps ─────────────────────────────────────────────────────────────
24
+ * ① `t(key, undefined)` loses the locale and answers in English, so `params` is always passed - the
25
+ * spread below makes an object even when a refusal carries none.
26
+ * ② Never pass a **value** to `t` as a key: `:` reads as a namespace separator and cuts `urn:epc:…`
27
+ * apart. Values travel in `params` only.
28
+ *
29
+ * ── Why it lives in the shell ─────────────────────────────────────────────
30
+ * operato-plant wrote this first and operato-warehouse copied it; worklist is the third consumer as
31
+ * the work-door screens move there (ADR-0069 decision 3). Everything it needs is `graphql`,
32
+ * `type-graphql` and the request's `t`, all of which sit at or below the shell, so this is the floor
33
+ * that every consumer already stands on. The translations stay with each module: `refusalKey` names
34
+ * `error.<code>` inside the caller's own namespace, and a module that does not declare the key falls
35
+ * back to the English sentence rather than printing the key.
36
+ */
37
+ class Refusal extends Error {
38
+ /**
39
+ * @param code this refusal's name (kebab). It states **the gate's fact**; a screen does not pick its
40
+ * own sentence from this name.
41
+ * @param params the values the sentence is built from.
42
+ * @param english the sentence used when the translation key is missing. An English sentence reads
43
+ * better than the key printed raw.
44
+ */
45
+ constructor(code, params, english) {
46
+ super(english);
47
+ this.name = 'Refusal';
48
+ this.code = code;
49
+ this.params = params;
50
+ }
51
+ }
52
+ exports.Refusal = Refusal;
53
+ function refusalKey(code) {
54
+ return `error.${code}`;
55
+ }
56
+ /**
57
+ * **The one place a refusal becomes a sentence.** The resolver wrapper and the worklist callbacks both
58
+ * call this, so the two exits cannot drift apart.
59
+ *
60
+ * Apollo's own `extensions.code` gets `BAD_USER_INPUT`: a refusal is not a server error, and left empty
61
+ * Apollo would fill in `INTERNAL_SERVER_ERROR`. The name rides separately in `extensions.refusal`; its
62
+ * presence is what tells a client the server refused on purpose.
63
+ */
64
+ function speakRefusal(refusal, context) {
65
+ const sentence = context.t
66
+ ? /* Always pass params - `undefined` here loses the locale and answers in English. */
67
+ context.t(refusalKey(refusal.code), { ...refusal.params, defaultValue: refusal.message })
68
+ : refusal.message;
69
+ return new graphql_1.GraphQLError(sentence, {
70
+ originalError: refusal,
71
+ extensions: { code: 'BAD_USER_INPUT', refusal: { code: refusal.code, params: refusal.params } }
72
+ });
73
+ }
74
+ /** Rethrow a refusal as a sentence in the caller's language; anything else passes through untouched. */
75
+ function rethrowSpoken(error, context) {
76
+ throw error instanceof Refusal ? speakRefusal(error, context) : error;
77
+ }
78
+ /** A resolver class passes through this (`@UseMiddleware(SpeakRefusals)`). */
79
+ const SpeakRefusals = async ({ context }, next) => {
80
+ try {
81
+ return await next();
82
+ }
83
+ catch (error) {
84
+ rethrowSpoken(error, context);
85
+ }
86
+ };
87
+ exports.SpeakRefusals = SpeakRefusals;
88
+ /**
89
+ * An activity callback runs inside a worklist mutation, not the module's own resolver, so
90
+ * `SpeakRefusals` never sees what it throws. Declarations wrap their callback in this: the same
91
+ * `speakRefusal`, the other exit.
92
+ */
93
+ function speaksRefusals(callback) {
94
+ return async (instance, addendum, context) => {
95
+ try {
96
+ await callback(instance, addendum, context);
97
+ }
98
+ catch (error) {
99
+ rethrowSpoken(error, context);
100
+ }
101
+ };
102
+ }
103
+ //# sourceMappingURL=refusal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../server/refusal/refusal.ts"],"names":[],"mappings":";;;AAqDA,gCAEC;AAYD,oCAUC;AAGD,sCAEC;AAgBD,wCAUC;AA5GD,qCAAsC;AAGtC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAa,OAAQ,SAAQ,KAAK;IAIhC;;;;;;OAMG;IACH,YAAY,IAAY,EAAE,MAAqB,EAAE,OAAe;QAC9D,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,SAAS,CAAA;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;CACF;AAjBD,0BAiBC;AAKD,SAAgB,UAAU,CAAC,IAAY;IACrC,OAAO,SAAS,IAAI,EAAE,CAAA;AACxB,CAAC;AAID;;;;;;;GAOG;AACH,SAAgB,YAAY,CAAC,OAAgB,EAAE,OAA0B;IACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC;QACxB,CAAC,CAAC,oFAAoF;YACpF,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3F,CAAC,CAAC,OAAO,CAAC,OAAO,CAAA;IAEnB,OAAO,IAAI,sBAAY,CAAC,QAAQ,EAAE;QAChC,aAAa,EAAE,OAAO;QACtB,UAAU,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE;KAChG,CAAC,CAAA;AACJ,CAAC;AAED,wGAAwG;AACxG,SAAgB,aAAa,CAAC,KAAc,EAAE,OAA0B;IACtE,MAAM,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACvE,CAAC;AAED,8EAA8E;AACvE,MAAM,aAAa,GAAsB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE;IAC1E,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAA;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC/B,CAAC;AACH,CAAC,CAAA;AANY,QAAA,aAAa,iBAMzB;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAC5B,QAAuE;IAEvE,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC3C,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { GraphQLError } from 'graphql'\nimport type { MiddlewareFn } from 'type-graphql'\n\n/**\n * **A refusal the server knows and names** (ADR-0054).\n *\n * ── Why a gate does not throw a sentence ──────────────────────────────────\n * A gate that throws an English sentence puts English on a Korean screen. A screen that translates\n * it is then writing the reason itself, and the gate and the screen end up with two accounts of the\n * same refusal. So the gate throws a name and values, and the sentence is made where the request's\n * language is known.\n *\n * ── What gets stored is the name ──────────────────────────────────────────\n * Never write the sentence onto an event or a work row. A sentence differs per reader and the wording\n * changes later; a stored one freezes that day's language. The `code` is the contract, and it travels\n * unchanged to the client (ADR-0042 - no mapping table on the way out).\n *\n * ── Two traps ─────────────────────────────────────────────────────────────\n * ① `t(key, undefined)` loses the locale and answers in English, so `params` is always passed - the\n * spread below makes an object even when a refusal carries none.\n * ② Never pass a **value** to `t` as a key: `:` reads as a namespace separator and cuts `urn:epc:…`\n * apart. Values travel in `params` only.\n *\n * ── Why it lives in the shell ─────────────────────────────────────────────\n * operato-plant wrote this first and operato-warehouse copied it; worklist is the third consumer as\n * the work-door screens move there (ADR-0069 decision 3). Everything it needs is `graphql`,\n * `type-graphql` and the request's `t`, all of which sit at or below the shell, so this is the floor\n * that every consumer already stands on. The translations stay with each module: `refusalKey` names\n * `error.<code>` inside the caller's own namespace, and a module that does not declare the key falls\n * back to the English sentence rather than printing the key.\n */\nexport class Refusal extends Error {\n readonly code: string\n readonly params: RefusalParams\n\n /**\n * @param code this refusal's name (kebab). It states **the gate's fact**; a screen does not pick its\n * own sentence from this name.\n * @param params the values the sentence is built from.\n * @param english the sentence used when the translation key is missing. An English sentence reads\n * better than the key printed raw.\n */\n constructor(code: string, params: RefusalParams, english: string) {\n super(english)\n this.name = 'Refusal'\n this.code = code\n this.params = params\n }\n}\n\n/** Values a refusal sentence is built from. Scalars only: they travel in the GraphQL response as they are. */\nexport type RefusalParams = Record<string, string | number | boolean | null>\n\nexport function refusalKey(code: string): string {\n return `error.${code}`\n}\n\ntype Translate = (key: string, options?: Record<string, unknown>) => string\n\n/**\n * **The one place a refusal becomes a sentence.** The resolver wrapper and the worklist callbacks both\n * call this, so the two exits cannot drift apart.\n *\n * Apollo's own `extensions.code` gets `BAD_USER_INPUT`: a refusal is not a server error, and left empty\n * Apollo would fill in `INTERNAL_SERVER_ERROR`. The name rides separately in `extensions.refusal`; its\n * presence is what tells a client the server refused on purpose.\n */\nexport function speakRefusal(refusal: Refusal, context: { t?: Translate }): GraphQLError {\n const sentence = context.t\n ? /* Always pass params - `undefined` here loses the locale and answers in English. */\n context.t(refusalKey(refusal.code), { ...refusal.params, defaultValue: refusal.message })\n : refusal.message\n\n return new GraphQLError(sentence, {\n originalError: refusal,\n extensions: { code: 'BAD_USER_INPUT', refusal: { code: refusal.code, params: refusal.params } }\n })\n}\n\n/** Rethrow a refusal as a sentence in the caller's language; anything else passes through untouched. */\nexport function rethrowSpoken(error: unknown, context: { t?: Translate }): never {\n throw error instanceof Refusal ? speakRefusal(error, context) : error\n}\n\n/** A resolver class passes through this (`@UseMiddleware(SpeakRefusals)`). */\nexport const SpeakRefusals: MiddlewareFn<any> = async ({ context }, next) => {\n try {\n return await next()\n } catch (error) {\n rethrowSpoken(error, context)\n }\n}\n\n/**\n * An activity callback runs inside a worklist mutation, not the module's own resolver, so\n * `SpeakRefusals` never sees what it throws. Declarations wrap their callback in this: the same\n * `speakRefusal`, the other exit.\n */\nexport function speaksRefusals(\n callback: (instance: any, addendum: any, context: any) => Promise<void>\n): (instance: any, addendum: any, context: any) => Promise<void> {\n return async (instance, addendum, context) => {\n try {\n await callback(instance, addendum, context)\n } catch (error) {\n rethrowSpoken(error, context)\n }\n }\n}\n"]}