@operato/board 10.29.26 → 10.29.28

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,17 +1,5 @@
1
- /** Which type map a page draws from: the active one, or the candidate in a preview. */
2
- export type FigureMapChoice = 'ACTIVE' | 'CANDIDATE';
3
- /** The type map release a page registered its figures from. */
4
- export interface FigureMapRelease {
5
- map: FigureMapChoice;
6
- version: number;
7
- revision: number;
8
- }
9
- /** What `figureBlueprints` answers: one map release, an item or a refusal per name asked. */
1
+ /** What `figureBlueprints` answers: an item or a refusal per name asked. */
10
2
  export interface FigureBlueprintsAnswer {
11
- release: {
12
- version: number;
13
- revision: number;
14
- } | null;
15
3
  items: {
16
4
  type: string;
17
5
  ref: string;
@@ -23,7 +11,7 @@ export interface FigureBlueprintsAnswer {
23
11
  message: string;
24
12
  }[];
25
13
  }
26
- export type BlueprintsFetch = (types: readonly string[], map: FigureMapChoice) => Promise<FigureBlueprintsAnswer>;
14
+ export type BlueprintsFetch = (types: readonly string[]) => Promise<FigureBlueprintsAnswer>;
27
15
  /** Why a type was not registered, for the stand-in. The shape things-scene's seat reads. */
28
16
  type Refusals = {
29
17
  refused: {
@@ -31,18 +19,8 @@ type Refusals = {
31
19
  message: string;
32
20
  }[];
33
21
  };
34
- export declare const FIGURE_TYPE_MAP_CHANGED = "figure-type-map-changed";
35
- /**
36
- * Asks the server. An active map is asked without the argument, so an authoring server whose door
37
- * has no `map` (it serves the latest release) answers the same query shape.
38
- */
22
+ /** Asks the server. A board server and an authoring server answer the same query shape. */
39
23
  export declare const fetchFigureBlueprints: BlueprintsFetch;
40
- /** Sets which map this page draws from. A preview page sets CANDIDATE before its board opens. */
41
- export declare function setFigureMapChoice(next: FigureMapChoice): void;
42
- /** The map release this page registered its figures from, or null before any (or from an authoring server). */
43
- export declare function figureMapReleaseOfPage(): FigureMapRelease | null;
44
- /** For tests: forget the page's map release and draw from the active map again. */
45
- export declare function resetFigureMapPage(): void;
46
24
  /**
47
25
  * Builds the resolver that fills things-scene's seat.
48
26
  *
@@ -4,26 +4,16 @@
4
4
  import gql from 'graphql-tag';
5
5
  import { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene';
6
6
  import { client, gqlContext } from '@operato/graphql';
7
- import { i18next } from '@operato/i18n';
8
- export const FIGURE_TYPE_MAP_CHANGED = 'figure-type-map-changed';
9
7
  const FETCH_FIGURE_BLUEPRINTS = gql `
10
- query FetchFigureBlueprints($types: [String!]!, $map: FigureTypeMapState) {
11
- figureBlueprints(types: $types, map: $map)
12
- }
13
- `;
14
- const FETCH_FIGURE_BLUEPRINTS_LATEST = gql `
15
- query FetchFigureBlueprintsLatest($types: [String!]!) {
8
+ query FetchFigureBlueprints($types: [String!]!) {
16
9
  figureBlueprints(types: $types)
17
10
  }
18
11
  `;
19
- /**
20
- * Asks the server. An active map is asked without the argument, so an authoring server whose door
21
- * has no `map` (it serves the latest release) answers the same query shape.
22
- */
23
- export const fetchFigureBlueprints = async (types, map) => {
12
+ /** Asks the server. A board server and an authoring server answer the same query shape. */
13
+ export const fetchFigureBlueprints = async (types) => {
24
14
  const response = await client.query({
25
- query: map === 'CANDIDATE' ? FETCH_FIGURE_BLUEPRINTS : FETCH_FIGURE_BLUEPRINTS_LATEST,
26
- variables: map === 'CANDIDATE' ? { types: [...types], map } : { types: [...types] },
15
+ query: FETCH_FIGURE_BLUEPRINTS,
16
+ variables: { types: [...types] },
27
17
  context: gqlContext(),
28
18
  fetchPolicy: 'network-only'
29
19
  });
@@ -37,22 +27,6 @@ export const fetchFigureBlueprints = async (types, map) => {
37
27
  }
38
28
  return response.data.figureBlueprints;
39
29
  };
40
- let choice = 'ACTIVE';
41
- let pageRelease = null;
42
- /** Sets which map this page draws from. A preview page sets CANDIDATE before its board opens. */
43
- export function setFigureMapChoice(next) {
44
- choice = next;
45
- }
46
- /** The map release this page registered its figures from, or null before any (or from an authoring server). */
47
- export function figureMapReleaseOfPage() {
48
- return pageRelease;
49
- }
50
- /** For tests: forget the page's map release and draw from the active map again. */
51
- export function resetFigureMapPage() {
52
- pageRelease = null;
53
- choice = 'ACTIVE';
54
- }
55
- const sameRelease = (a, b) => a.map === b.map && a.version === b.version && a.revision === b.revision;
56
30
  /**
57
31
  * Builds the resolver that fills things-scene's seat.
58
32
  *
@@ -64,18 +38,7 @@ export function createFigureResolver(fetch = fetchFigureBlueprints) {
64
38
  if (asking.length === 0) {
65
39
  return;
66
40
  }
67
- const map = choice;
68
- const answer = await fetch(asking, map);
69
- if (answer.release) {
70
- const now = { map, version: answer.release.version, revision: answer.release.revision };
71
- if (pageRelease && !sameRelease(pageRelease, now)) {
72
- /* Another map release than the figures drawn here: none is registered, the app offers a reopen. */
73
- window.dispatchEvent(new CustomEvent(FIGURE_TYPE_MAP_CHANGED, { detail: { was: pageRelease, now } }));
74
- const message = i18next.t('text.figure-type-map-changed', { defaultValue: 'the type map changed. reopen to draw it.' });
75
- return { refused: asking.map(type => ({ type, message })) };
76
- }
77
- pageRelease = pageRelease !== null && pageRelease !== void 0 ? pageRelease : now;
78
- }
41
+ const answer = await fetch(asking);
79
42
  const refused = answer.refused
80
43
  .filter(refusal => asking.includes(refusal.type))
81
44
  .map(refusal => ({ type: refusal.type, message: refusal.message }));
@@ -1 +1 @@
1
- {"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport gql from 'graphql-tag'\n\nimport { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\n\n/*\n * Where a board gets the figures it names.\n *\n * ── Why the seat is filled here ─────────────────────────────────────────────\n * things-scene keeps the seat (`registerTypeResolver`) and knows nothing about figures, GraphQL or\n * who published what. The board package is where a saved model already gets its values from the\n * server, so this is where the types a model names, and nobody has registered, get fetched\n * (ADR-0063 결정 2 · ADR-0045).\n *\n * ── One question per board, by full name ─────────────────────────────────────\n * A board names a figure by its full name `{namespace}/{type}` (ADR-0114 결정 2). Every type the\n * model names that nobody registered is asked in one `figureBlueprints` query; a type already\n * registered -- a built-in component, a figure already drawing -- is never asked. The server answers\n * each name with an item or a refusal with its reason; items are registered under the name asked,\n * refusals go back to the seat so the stand-in shows why (ADR-0114 A3). Being unable to ask at all\n * rejects, and `resolveUnknownTypes` reports it in `failed`; the board still opens.\n *\n * ── One map release per page ─────────────────────────────────────────────────\n * A board server answers from one type map release `{ version, revision }` (ADR-0114 결정 12). The\n * page keeps the first release it registered from, together with which map it asked (active, or the\n * candidate in a preview). An answer from another release is not registered -- figures already drawn\n * would otherwise mix two maps -- and `figure-type-map-changed` tells the app to offer a reopen. An\n * authoring server answers with no release (its latest-release policy); then nothing is kept.\n */\n\n/** Which type map a page draws from: the active one, or the candidate in a preview. */\nexport type FigureMapChoice = 'ACTIVE' | 'CANDIDATE'\n\n/** The type map release a page registered its figures from. */\nexport interface FigureMapRelease {\n map: FigureMapChoice\n version: number\n revision: number\n}\n\n/** What `figureBlueprints` answers: one map release, an item or a refusal per name asked. */\nexport interface FigureBlueprintsAnswer {\n release: { version: number; revision: number } | null\n items: { type: string; ref: string; asset: unknown }[]\n refused: { type: string; code: string; message: string }[]\n}\n\nexport type BlueprintsFetch = (types: readonly string[], map: FigureMapChoice) => Promise<FigureBlueprintsAnswer>\n\n/** Why a type was not registered, for the stand-in. The shape things-scene's seat reads. */\ntype Refusals = { refused: { type: string; message: string }[] }\n\nexport const FIGURE_TYPE_MAP_CHANGED = 'figure-type-map-changed'\n\nconst FETCH_FIGURE_BLUEPRINTS = gql`\n query FetchFigureBlueprints($types: [String!]!, $map: FigureTypeMapState) {\n figureBlueprints(types: $types, map: $map)\n }\n`\n\nconst FETCH_FIGURE_BLUEPRINTS_LATEST = gql`\n query FetchFigureBlueprintsLatest($types: [String!]!) {\n figureBlueprints(types: $types)\n }\n`\n\n/**\n * Asks the server. An active map is asked without the argument, so an authoring server whose door\n * has no `map` (it serves the latest release) answers the same query shape.\n */\nexport const fetchFigureBlueprints: BlueprintsFetch = async (types, map) => {\n const response = await client.query({\n query: map === 'CANDIDATE' ? FETCH_FIGURE_BLUEPRINTS : FETCH_FIGURE_BLUEPRINTS_LATEST,\n variables: map === 'CANDIDATE' ? { types: [...types], map } : { types: [...types] },\n context: gqlContext(),\n fetchPolicy: 'network-only'\n })\n /*\n The client runs with errorPolicy 'all', so a rejected query comes back as a resolved response\n carrying `errors`. Without this the caller cannot tell \"the door said no\" from an empty answer.\n */\n const errors = (response as any).errors as { message?: string }[] | undefined\n if (errors?.length) {\n throw new Error(errors.map(e => e?.message ?? String(e)).join('\\n'))\n }\n return response.data.figureBlueprints as FigureBlueprintsAnswer\n}\n\nlet choice: FigureMapChoice = 'ACTIVE'\nlet pageRelease: FigureMapRelease | null = null\n\n/** Sets which map this page draws from. A preview page sets CANDIDATE before its board opens. */\nexport function setFigureMapChoice(next: FigureMapChoice): void {\n choice = next\n}\n\n/** The map release this page registered its figures from, or null before any (or from an authoring server). */\nexport function figureMapReleaseOfPage(): FigureMapRelease | null {\n return pageRelease\n}\n\n/** For tests: forget the page's map release and draw from the active map again. */\nexport function resetFigureMapPage(): void {\n pageRelease = null\n choice = 'ACTIVE'\n}\n\nconst sameRelease = (a: FigureMapRelease, b: FigureMapRelease) => a.map === b.map && a.version === b.version && a.revision === b.revision\n\n/**\n * Builds the resolver that fills things-scene's seat.\n *\n * `fetch` is a seam for tests — the default one talks to the server.\n */\nexport function createFigureResolver(fetch: BlueprintsFetch = fetchFigureBlueprints) {\n return async function resolveFigureTypes(types: readonly string[]): Promise<Refusals | undefined> {\n const asking = types.filter(type => !Component.register(type))\n if (asking.length === 0) {\n return\n }\n\n const map = choice\n const answer = await fetch(asking, map)\n\n if (answer.release) {\n const now: FigureMapRelease = { map, version: answer.release.version, revision: answer.release.revision }\n if (pageRelease && !sameRelease(pageRelease, now)) {\n /* Another map release than the figures drawn here: none is registered, the app offers a reopen. */\n window.dispatchEvent(new CustomEvent(FIGURE_TYPE_MAP_CHANGED, { detail: { was: pageRelease, now } }))\n const message = i18next.t('text.figure-type-map-changed', { defaultValue: 'the type map changed. reopen to draw it.' })\n return { refused: asking.map(type => ({ type, message })) }\n }\n pageRelease = pageRelease ?? now\n }\n\n const refused: Refusals['refused'] = answer.refused\n .filter(refusal => asking.includes(refusal.type))\n .map(refusal => ({ type: refusal.type, message: refusal.message }))\n\n for (const item of answer.items) {\n if (!asking.includes(item.type)) continue\n try {\n registerFigurePayload(item.type, item)\n } catch (error) {\n refused.push({ type: item.type, message: error instanceof Error ? error.message : String(error) })\n }\n }\n\n return { refused }\n }\n}\n\n/** Registers one figure under the name the board asked. Anything but a figure asset is refused by name. */\nexport function registerFigurePayload(type: string, payload: { asset?: unknown }): void {\n if (!payload?.asset || typeof payload.asset !== 'object') {\n throw new Error(`not a figure asset: ${type}`)\n }\n registerFigure(type, payload.asset as any)\n}\n\n/**\n * Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`\n * gets figures on its boards without registering anything itself. Returns the function that takes\n * the seat back.\n */\nexport function installFigureResolver(fetch: BlueprintsFetch = fetchFigureBlueprints): () => void {\n return registerTypeResolver(createFigureResolver(fetch) as any)\n}\n"]}
1
+ {"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAoCrD,MAAM,uBAAuB,GAAG,GAAG,CAAA;;;;CAIlC,CAAA;AAED,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAAoB,KAAK,EAAC,KAAK,EAAC,EAAE;IAClE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAClC,KAAK,EAAE,uBAAuB;QAC9B,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE;QAChC,OAAO,EAAE,UAAU,EAAE;QACrB,WAAW,EAAE,cAAc;KAC5B,CAAC,CAAA;IACF;;;MAGE;IACF,MAAM,MAAM,GAAI,QAAgB,CAAC,MAA4C,CAAA;IAC7E,IAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,WAAC,OAAA,MAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,mCAAI,MAAM,CAAC,CAAC,CAAC,CAAA,EAAA,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IACtE,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,gBAA0C,CAAA;AACjE,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAyB,qBAAqB;IACjF,OAAO,KAAK,UAAU,kBAAkB,CAAC,KAAwB;QAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;QAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAA;QAElC,MAAM,OAAO,GAAwB,MAAM,CAAC,OAAO;aAChD,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aAChD,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAErE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAQ;YACzC,IAAI,CAAC;gBACH,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACpG,CAAC;QACH,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,CAAA;IACpB,CAAC,CAAA;AACH,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,OAA4B;IAC9E,IAAI,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,KAAK,CAAA,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAA;IAChD,CAAC;IACD,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,KAAY,CAAC,CAAA;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAyB,qBAAqB;IAClF,OAAO,oBAAoB,CAAC,oBAAoB,CAAC,KAAK,CAAQ,CAAC,CAAA;AACjE,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport gql from 'graphql-tag'\n\nimport { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene'\nimport { client, gqlContext } from '@operato/graphql'\n\n/*\n * Where a board gets the figures it names.\n *\n * ── Why the seat is filled here ─────────────────────────────────────────────\n * things-scene keeps the seat (`registerTypeResolver`) and knows nothing about figures, GraphQL or\n * who published what. The board package is where a saved model already gets its values from the\n * server, so this is where the types a model names, and nobody has registered, get fetched\n * (ADR-0063 결정 2 · ADR-0045).\n *\n * ── One question per board, by full name ─────────────────────────────────────\n * A board names a figure by its full name `{namespace}/{type}` (ADR-0114 결정 2). Every type the\n * model names that nobody registered is asked in one `figureBlueprints` query; a type already\n * registered -- a built-in component, a figure already drawing -- is never asked. The server answers\n * each name with an item or a refusal with its reason; items are registered under the name asked,\n * refusals go back to the seat so the stand-in shows why (ADR-0114 A3). Being unable to ask at all\n * rejects, and `resolveUnknownTypes` reports it in `failed`; the board still opens.\n *\n * ── What draws ──────────────────────────────────────────────────────────────\n * A board server answers each name from its type map: the release the operator chose for it, by default the latest\n * imported (ADR-0114 결정 6 · 7, amended 2026-10-12). An authoring server answers with its latest release. Either way\n * the page registers what it is given; a figure already registered on the page stays until the page is opened again.\n */\n\n/** What `figureBlueprints` answers: an item or a refusal per name asked. */\nexport interface FigureBlueprintsAnswer {\n items: { type: string; ref: string; asset: unknown }[]\n refused: { type: string; code: string; message: string }[]\n}\n\nexport type BlueprintsFetch = (types: readonly string[]) => Promise<FigureBlueprintsAnswer>\n\n/** Why a type was not registered, for the stand-in. The shape things-scene's seat reads. */\ntype Refusals = { refused: { type: string; message: string }[] }\n\nconst FETCH_FIGURE_BLUEPRINTS = gql`\n query FetchFigureBlueprints($types: [String!]!) {\n figureBlueprints(types: $types)\n }\n`\n\n/** Asks the server. A board server and an authoring server answer the same query shape. */\nexport const fetchFigureBlueprints: BlueprintsFetch = async types => {\n const response = await client.query({\n query: FETCH_FIGURE_BLUEPRINTS,\n variables: { types: [...types] },\n context: gqlContext(),\n fetchPolicy: 'network-only'\n })\n /*\n The client runs with errorPolicy 'all', so a rejected query comes back as a resolved response\n carrying `errors`. Without this the caller cannot tell \"the door said no\" from an empty answer.\n */\n const errors = (response as any).errors as { message?: string }[] | undefined\n if (errors?.length) {\n throw new Error(errors.map(e => e?.message ?? String(e)).join('\\n'))\n }\n return response.data.figureBlueprints as FigureBlueprintsAnswer\n}\n\n/**\n * Builds the resolver that fills things-scene's seat.\n *\n * `fetch` is a seam for tests — the default one talks to the server.\n */\nexport function createFigureResolver(fetch: BlueprintsFetch = fetchFigureBlueprints) {\n return async function resolveFigureTypes(types: readonly string[]): Promise<Refusals | undefined> {\n const asking = types.filter(type => !Component.register(type))\n if (asking.length === 0) {\n return\n }\n\n const answer = await fetch(asking)\n\n const refused: Refusals['refused'] = answer.refused\n .filter(refusal => asking.includes(refusal.type))\n .map(refusal => ({ type: refusal.type, message: refusal.message }))\n\n for (const item of answer.items) {\n if (!asking.includes(item.type)) continue\n try {\n registerFigurePayload(item.type, item)\n } catch (error) {\n refused.push({ type: item.type, message: error instanceof Error ? error.message : String(error) })\n }\n }\n\n return { refused }\n }\n}\n\n/** Registers one figure under the name the board asked. Anything but a figure asset is refused by name. */\nexport function registerFigurePayload(type: string, payload: { asset?: unknown }): void {\n if (!payload?.asset || typeof payload.asset !== 'object') {\n throw new Error(`not a figure asset: ${type}`)\n }\n registerFigure(type, payload.asset as any)\n}\n\n/**\n * Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`\n * gets figures on its boards without registering anything itself. Returns the function that takes\n * the seat back.\n */\nexport function installFigureResolver(fetch: BlueprintsFetch = fetchFigureBlueprints): () => void {\n return registerTypeResolver(createFigureResolver(fetch) as any)\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { expect } from '@open-wc/testing';
2
2
  import { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene';
3
- import { createFigureResolver, FIGURE_TYPE_MAP_CHANGED, figureMapReleaseOfPage, resetFigureMapPage, setFigureMapChoice } from '../src/figure-provider.js';
3
+ import { createFigureResolver } from '../src/figure-provider.js';
4
4
  /**
5
5
  * The path a figure takes to a board (ADR-0063 결정 2, ADR-0114): one batch question by full name.
6
6
  *
@@ -57,20 +57,18 @@ const figureAssetOf = (type) => ({
57
57
  placement: 'floor'
58
58
  }
59
59
  });
60
- /** A server that answers from a table, from one map release, and records each question. */
61
- function serverOf(published, release = { version: 1, revision: 1 }, refusals = {}) {
60
+ /** A server that answers from a table and records each question. */
61
+ function serverOf(published, refusals = {}) {
62
62
  const asked = [];
63
- const state = { release };
64
63
  return {
65
64
  asked,
66
- state,
67
- fetch: async (types, map) => {
68
- asked.push({ types: [...types], map });
65
+ fetch: async (types) => {
66
+ asked.push([...types]);
69
67
  const items = types.filter(type => published[type]).map(type => ({ type, ref: `${type}@1`, asset: published[type] }));
70
68
  const refused = types
71
69
  .filter(type => !published[type])
72
70
  .map(type => { var _a; return ({ type, code: 'figure-not-in-type-map', message: (_a = refusals[type]) !== null && _a !== void 0 ? _a : `${type} is not in the type map` }); });
73
- return { release: state.release, items, refused };
71
+ return { items, refused };
74
72
  }
75
73
  };
76
74
  }
@@ -78,13 +76,12 @@ const name = (label) => `hatiolab.com/${label}-${Math.random().toString(36).slic
78
76
  describe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번에 묻는다', () => {
79
77
  afterEach(() => {
80
78
  resetFigures();
81
- resetFigureMapPage();
82
79
  });
83
80
  it('등록되지 않은 type 을 대소문자와 관계없이 한 번에 묻는다 — 이미 등록된 것은 묻지 않는다', async () => {
84
81
  const mixer = name('eq-mixer');
85
82
  const { asked, fetch } = serverOf({ [mixer]: figureAssetOf(mixer) });
86
83
  await createFigureResolver(fetch)([mixer, 'rect', 'Conveyor', 'text', 'VEH_AGV_01']);
87
- expect(asked).to.deep.equal([{ types: [mixer, 'Conveyor', 'VEH_AGV_01'], map: 'ACTIVE' }]);
84
+ expect(asked).to.deep.equal([[mixer, 'Conveyor', 'VEH_AGV_01']]);
88
85
  });
89
86
  it('이미 등록된 이름은 묻지 않고, figure 가 그 컴포넌트를 덮지 않는다', async () => {
90
87
  const taken = name('taken');
@@ -108,7 +105,7 @@ describe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번
108
105
  const missing = name('missing');
109
106
  const brokenAsset = figureAssetOf(broken);
110
107
  brokenAsset.appearance = [];
111
- const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { version: 1, revision: 1 }, { [missing]: '타입 맵에 없습니다' });
108
+ const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { [missing]: '타입 맵에 없습니다' });
112
109
  const answer = await createFigureResolver(fetch)([good, broken, missing]);
113
110
  expect(hasFigure(good)).to.equal(true);
114
111
  expect(hasFigure(broken)).to.equal(false);
@@ -128,52 +125,16 @@ describe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번
128
125
  }
129
126
  expect(message).to.contain('no route to the door');
130
127
  });
131
- it('★ 페이지는 처음 받은 맵 릴리스를 기억하고, 후보의 revision 이 바뀐 답은 등록하지 않는다', async () => {
128
+ it('★ 같은 페이지에서 나중에 묻는 이름도 그때 서버가 준 것으로 등록한다 — 먼저 등록한 것은 그대로다', async () => {
132
129
  const first = name('first');
133
130
  const later = name('later');
134
- const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) }, { version: 2, revision: 3 });
131
+ const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) });
135
132
  const resolver = createFigureResolver(server.fetch);
136
- setFigureMapChoice('CANDIDATE');
137
133
  await resolver([first]);
138
- expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 });
139
- let changed = null;
140
- const listen = (e) => (changed = e.detail);
141
- window.addEventListener(FIGURE_TYPE_MAP_CHANGED, listen);
142
- try {
143
- server.state.release = { version: 2, revision: 4 };
144
- const answer = await resolver([later]);
145
- expect(hasFigure(later)).to.equal(false);
146
- expect(answer.refused.map(r => r.type)).to.deep.equal([later]);
147
- expect(changed).to.deep.equal({ was: { map: 'CANDIDATE', version: 2, revision: 3 }, now: { map: 'CANDIDATE', version: 2, revision: 4 } });
148
- expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 });
149
- }
150
- finally {
151
- window.removeEventListener(FIGURE_TYPE_MAP_CHANGED, listen);
152
- }
153
- });
154
- it('★ 활성 맵과 후보 맵의 등록을 한 페이지에서 섞지 않는다', async () => {
155
- const active = name('active');
156
- const preview = name('preview');
157
- const server = serverOf({ [active]: figureAssetOf(active), [preview]: figureAssetOf(preview) }, { version: 1, revision: 1 });
158
- const resolver = createFigureResolver(server.fetch);
159
- await resolver([active]);
160
- expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'ACTIVE', version: 1, revision: 1 });
161
- setFigureMapChoice('CANDIDATE');
162
- const answer = await resolver([preview]);
163
- expect(server.asked.map(a => a.map)).to.deep.equal(['ACTIVE', 'CANDIDATE']);
164
- expect(hasFigure(preview)).to.equal(false);
165
- expect(answer.refused.map(r => r.type)).to.deep.equal([preview]);
166
- });
167
- it('맵 릴리스가 없는 서버(저작 서버의 최신 정책)는 기억하지 않는다', async () => {
168
- const a = name('a');
169
- const b = name('b');
170
- const server = serverOf({ [a]: figureAssetOf(a), [b]: figureAssetOf(b) }, null);
171
- const resolver = createFigureResolver(server.fetch);
172
- await resolver([a]);
173
- await resolver([b]);
174
- expect(figureMapReleaseOfPage()).to.equal(null);
175
- expect(hasFigure(a)).to.equal(true);
176
- expect(hasFigure(b)).to.equal(true);
134
+ await resolver([first, later]);
135
+ expect(server.asked).to.deep.equal([[first], [later]]);
136
+ expect(hasFigure(first)).to.equal(true);
137
+ expect(hasFigure(later)).to.equal(true);
177
138
  });
178
139
  it('씬의 자리에 앉아 동작한다 — 받은 것은 그려지고 나머지는 자리표시로 남는다', async () => {
179
140
  const onBoard = name('on-board');
@@ -1 +1 @@
1
- {"version":3,"file":"figure-provider.test.js","sourceRoot":"","sources":["../../test/figure-provider.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene'\n\nimport {\n createFigureResolver,\n FIGURE_TYPE_MAP_CHANGED,\n figureMapReleaseOfPage,\n resetFigureMapPage,\n setFigureMapChoice,\n type FigureBlueprintsAnswer\n} from '../src/figure-provider.js'\n\n/**\n * The path a figure takes to a board (ADR-0063 결정 2, ADR-0114): one batch question by full name.\n *\n * things-scene holds the seat and knows nothing about figures; this package fills it. What is\n * checked here is the part that belongs to the board: which names are asked about, what happens to\n * the answer, and how \"nobody published this\" stays apart from \"could not ask\".\n *\n * The payload below is written out rather than built with figure-model: this package does not read it, it passes it\n * to the scene's own registration, and a fixture that drifts from the format should fail loudly here rather than\n * draw nothing at runtime.\n */\n\n/** What the door hands over: a figure asset. One box standing on the floor, its room declared. */\nconst figureAssetOf = (type: string) => ({\n version: 'figure-asset-1',\n name: type,\n document: {\n version: 'figure-prototype-1',\n model: {\n version: 'r3-spatial-1',\n inputs: [],\n constants: [\n { id: 'w', unit: 'mm', value: 100 },\n { id: 'half', unit: 'mm', value: 50 },\n { id: 'minusHalf', unit: 'mm', value: -50 },\n { id: 'zero', unit: 'mm', value: 0 },\n { id: 'a0', unit: 'deg', value: 0 }\n ],\n nodes: [\n {\n id: 'body.shape',\n op: 'extrude@1',\n args: ['zero', 'w', 'w', 'w'],\n outputs: { shape: 'body.s' },\n params: { frame: 'body.local', axis: 'y', section: 'rectangle' }\n },\n {\n id: 'body.pose',\n op: 'rigid@1',\n args: ['zero', 'zero', 'zero', 'a0', 'a0', 'a0'],\n outputs: { pose: 'body.p' },\n params: { from: 'body.local', to: 'asset' }\n },\n { id: 'body', op: 'place@1', args: ['body.s', 'body.p'], outputs: { placed: 'body.placed' } }\n ]\n },\n capabilities: { version: 'r3-capability-3', assetFrame: 'asset', bindings: [] }\n },\n designInputs: {},\n stateDefaults: {},\n palette: { 'palette.primary': '#477b9e' },\n appearance: [{ target: 'body', material: { token: 'palette.primary' } }],\n occupancy: {\n bounds: { x: { min: 'minusHalf', max: 'half' }, y: { min: 'zero', max: 'w' }, z: { min: 'minusHalf', max: 'half' } },\n placement: 'floor'\n }\n})\n\n/** A server that answers from a table, from one map release, and records each question. */\nfunction serverOf(\n published: Record<string, unknown>,\n release: { version: number; revision: number } | null = { version: 1, revision: 1 },\n refusals: Record<string, string> = {}\n) {\n const asked: { types: string[]; map: string }[] = []\n const state = { release }\n return {\n asked,\n state,\n fetch: async (types: readonly string[], map: string): Promise<FigureBlueprintsAnswer> => {\n asked.push({ types: [...types], map })\n const items = types.filter(type => published[type]).map(type => ({ type, ref: `${type}@1`, asset: published[type] }))\n const refused = types\n .filter(type => !published[type])\n .map(type => ({ type, code: 'figure-not-in-type-map', message: refusals[type] ?? `${type} is not in the type map` }))\n return { release: state.release, items, refused }\n }\n }\n}\n\nconst name = (label: string) => `hatiolab.com/${label}-${Math.random().toString(36).slice(2, 8)}`\n\ndescribe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번에 묻는다', () => {\n afterEach(() => {\n resetFigures()\n resetFigureMapPage()\n })\n\n it('등록되지 않은 type 을 대소문자와 관계없이 한 번에 묻는다 — 이미 등록된 것은 묻지 않는다', async () => {\n const mixer = name('eq-mixer')\n const { asked, fetch } = serverOf({ [mixer]: figureAssetOf(mixer) })\n\n await createFigureResolver(fetch)([mixer, 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])\n\n expect(asked).to.deep.equal([{ types: [mixer, 'Conveyor', 'VEH_AGV_01'], map: 'ACTIVE' }])\n })\n\n it('이미 등록된 이름은 묻지 않고, figure 가 그 컴포넌트를 덮지 않는다', async () => {\n const taken = name('taken')\n const { asked, fetch } = serverOf({ [taken]: figureAssetOf(taken) })\n Component.register(taken, class Taken extends Component {} as never)\n\n await createFigureResolver(fetch)([taken])\n\n expect(asked).to.deep.equal([])\n expect(hasFigure(taken)).to.equal(false)\n })\n\n it('받은 자산을 물은 이름으로 등록한다', async () => {\n const conveyor = name('conveyor')\n const { fetch } = serverOf({ [conveyor]: figureAssetOf(conveyor) })\n\n await createFigureResolver(fetch)([conveyor])\n\n expect(hasFigure(conveyor)).to.equal(true)\n expect(Component.register(conveyor)).to.not.equal(undefined)\n })\n\n it('★ 그리지 못한 이름은 서버가 준 이유를 그대로 돌려준다 — 읽을 수 없는 자산도 그 이름만 이유를 받는다', async () => {\n const good = name('good')\n const broken = name('broken')\n const missing = name('missing')\n const brokenAsset = figureAssetOf(broken)\n brokenAsset.appearance = []\n const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { version: 1, revision: 1 }, { [missing]: '타입 맵에 없습니다' })\n\n const answer = await createFigureResolver(fetch)([good, broken, missing])\n\n expect(hasFigure(good)).to.equal(true)\n expect(hasFigure(broken)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([missing, broken])\n expect(answer!.refused[0].message).to.equal('타입 맵에 없습니다')\n })\n\n it('물을 수 없으면 거절한다 — 씬은 실패로 모으고 보드는 열린다', async () => {\n const fetch = async () => {\n throw new Error('no route to the door')\n }\n let message = ''\n try {\n await createFigureResolver(fetch as never)([name('x')])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n expect(message).to.contain('no route to the door')\n })\n\n it('★ 페이지는 처음 받은 맵 릴리스를 기억하고, 후보의 revision 이 바뀐 답은 등록하지 않는다', async () => {\n const first = name('first')\n const later = name('later')\n const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) }, { version: 2, revision: 3 })\n const resolver = createFigureResolver(server.fetch)\n setFigureMapChoice('CANDIDATE')\n\n await resolver([first])\n expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 })\n\n let changed: any = null\n const listen = (e: Event) => (changed = (e as CustomEvent).detail)\n window.addEventListener(FIGURE_TYPE_MAP_CHANGED, listen)\n try {\n server.state.release = { version: 2, revision: 4 }\n const answer = await resolver([later])\n expect(hasFigure(later)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([later])\n expect(changed).to.deep.equal({ was: { map: 'CANDIDATE', version: 2, revision: 3 }, now: { map: 'CANDIDATE', version: 2, revision: 4 } })\n expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 })\n } finally {\n window.removeEventListener(FIGURE_TYPE_MAP_CHANGED, listen)\n }\n })\n\n it('★ 활성 맵과 후보 맵의 등록을 한 페이지에서 섞지 않는다', async () => {\n const active = name('active')\n const preview = name('preview')\n const server = serverOf({ [active]: figureAssetOf(active), [preview]: figureAssetOf(preview) }, { version: 1, revision: 1 })\n const resolver = createFigureResolver(server.fetch)\n\n await resolver([active])\n expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'ACTIVE', version: 1, revision: 1 })\n\n setFigureMapChoice('CANDIDATE')\n const answer = await resolver([preview])\n\n expect(server.asked.map(a => a.map)).to.deep.equal(['ACTIVE', 'CANDIDATE'])\n expect(hasFigure(preview)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([preview])\n })\n\n it('맵 릴리스가 없는 서버(저작 서버의 최신 정책)는 기억하지 않는다', async () => {\n const a = name('a')\n const b = name('b')\n const server = serverOf({ [a]: figureAssetOf(a), [b]: figureAssetOf(b) }, null)\n const resolver = createFigureResolver(server.fetch)\n\n await resolver([a])\n await resolver([b])\n\n expect(figureMapReleaseOfPage()).to.equal(null)\n expect(hasFigure(a)).to.equal(true)\n expect(hasFigure(b)).to.equal(true)\n })\n\n it('씬의 자리에 앉아 동작한다 — 받은 것은 그려지고 나머지는 자리표시로 남는다', async () => {\n const onBoard = name('on-board')\n const never = name('never')\n const { fetch } = serverOf({ [onBoard]: figureAssetOf(onBoard) })\n const takeBack = registerTypeResolver(createFigureResolver(fetch) as never)\n\n try {\n const model = {\n type: 'container',\n components: [\n { type: onBoard, left: 0, top: 0, width: 100, height: 100 },\n { type: never, left: 0, top: 0, width: 100, height: 100 }\n ]\n }\n const resolution = await resolveUnknownTypes(model)\n expect(resolution.unresolved).to.deep.equal([never])\n expect(resolution.failed).to.deep.equal([])\n expect(hasFigure(onBoard)).to.equal(true)\n } finally {\n takeBack()\n }\n })\n})\n"]}
1
+ {"version":3,"file":"figure-provider.test.js","sourceRoot":"","sources":["../../test/figure-provider.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene'\n\nimport { createFigureResolver, type FigureBlueprintsAnswer } from '../src/figure-provider.js'\n\n/**\n * The path a figure takes to a board (ADR-0063 결정 2, ADR-0114): one batch question by full name.\n *\n * things-scene holds the seat and knows nothing about figures; this package fills it. What is\n * checked here is the part that belongs to the board: which names are asked about, what happens to\n * the answer, and how \"nobody published this\" stays apart from \"could not ask\".\n *\n * The payload below is written out rather than built with figure-model: this package does not read it, it passes it\n * to the scene's own registration, and a fixture that drifts from the format should fail loudly here rather than\n * draw nothing at runtime.\n */\n\n/** What the door hands over: a figure asset. One box standing on the floor, its room declared. */\nconst figureAssetOf = (type: string) => ({\n version: 'figure-asset-1',\n name: type,\n document: {\n version: 'figure-prototype-1',\n model: {\n version: 'r3-spatial-1',\n inputs: [],\n constants: [\n { id: 'w', unit: 'mm', value: 100 },\n { id: 'half', unit: 'mm', value: 50 },\n { id: 'minusHalf', unit: 'mm', value: -50 },\n { id: 'zero', unit: 'mm', value: 0 },\n { id: 'a0', unit: 'deg', value: 0 }\n ],\n nodes: [\n {\n id: 'body.shape',\n op: 'extrude@1',\n args: ['zero', 'w', 'w', 'w'],\n outputs: { shape: 'body.s' },\n params: { frame: 'body.local', axis: 'y', section: 'rectangle' }\n },\n {\n id: 'body.pose',\n op: 'rigid@1',\n args: ['zero', 'zero', 'zero', 'a0', 'a0', 'a0'],\n outputs: { pose: 'body.p' },\n params: { from: 'body.local', to: 'asset' }\n },\n { id: 'body', op: 'place@1', args: ['body.s', 'body.p'], outputs: { placed: 'body.placed' } }\n ]\n },\n capabilities: { version: 'r3-capability-3', assetFrame: 'asset', bindings: [] }\n },\n designInputs: {},\n stateDefaults: {},\n palette: { 'palette.primary': '#477b9e' },\n appearance: [{ target: 'body', material: { token: 'palette.primary' } }],\n occupancy: {\n bounds: { x: { min: 'minusHalf', max: 'half' }, y: { min: 'zero', max: 'w' }, z: { min: 'minusHalf', max: 'half' } },\n placement: 'floor'\n }\n})\n\n/** A server that answers from a table and records each question. */\nfunction serverOf(published: Record<string, unknown>, refusals: Record<string, string> = {}) {\n const asked: string[][] = []\n return {\n asked,\n fetch: async (types: readonly string[]): Promise<FigureBlueprintsAnswer> => {\n asked.push([...types])\n const items = types.filter(type => published[type]).map(type => ({ type, ref: `${type}@1`, asset: published[type] }))\n const refused = types\n .filter(type => !published[type])\n .map(type => ({ type, code: 'figure-not-in-type-map', message: refusals[type] ?? `${type} is not in the type map` }))\n return { items, refused }\n }\n }\n}\n\nconst name = (label: string) => `hatiolab.com/${label}-${Math.random().toString(36).slice(2, 8)}`\n\ndescribe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번에 묻는다', () => {\n afterEach(() => {\n resetFigures()\n })\n\n it('등록되지 않은 type 을 대소문자와 관계없이 한 번에 묻는다 — 이미 등록된 것은 묻지 않는다', async () => {\n const mixer = name('eq-mixer')\n const { asked, fetch } = serverOf({ [mixer]: figureAssetOf(mixer) })\n\n await createFigureResolver(fetch)([mixer, 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])\n\n expect(asked).to.deep.equal([[mixer, 'Conveyor', 'VEH_AGV_01']])\n })\n\n it('이미 등록된 이름은 묻지 않고, figure 가 그 컴포넌트를 덮지 않는다', async () => {\n const taken = name('taken')\n const { asked, fetch } = serverOf({ [taken]: figureAssetOf(taken) })\n Component.register(taken, class Taken extends Component {} as never)\n\n await createFigureResolver(fetch)([taken])\n\n expect(asked).to.deep.equal([])\n expect(hasFigure(taken)).to.equal(false)\n })\n\n it('받은 자산을 물은 이름으로 등록한다', async () => {\n const conveyor = name('conveyor')\n const { fetch } = serverOf({ [conveyor]: figureAssetOf(conveyor) })\n\n await createFigureResolver(fetch)([conveyor])\n\n expect(hasFigure(conveyor)).to.equal(true)\n expect(Component.register(conveyor)).to.not.equal(undefined)\n })\n\n it('★ 그리지 못한 이름은 서버가 준 이유를 그대로 돌려준다 — 읽을 수 없는 자산도 그 이름만 이유를 받는다', async () => {\n const good = name('good')\n const broken = name('broken')\n const missing = name('missing')\n const brokenAsset = figureAssetOf(broken)\n brokenAsset.appearance = []\n const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { [missing]: '타입 맵에 없습니다' })\n\n const answer = await createFigureResolver(fetch)([good, broken, missing])\n\n expect(hasFigure(good)).to.equal(true)\n expect(hasFigure(broken)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([missing, broken])\n expect(answer!.refused[0].message).to.equal('타입 맵에 없습니다')\n })\n\n it('물을 수 없으면 거절한다 — 씬은 실패로 모으고 보드는 열린다', async () => {\n const fetch = async () => {\n throw new Error('no route to the door')\n }\n let message = ''\n try {\n await createFigureResolver(fetch as never)([name('x')])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n expect(message).to.contain('no route to the door')\n })\n\n it('★ 같은 페이지에서 나중에 묻는 이름도 그때 서버가 준 것으로 등록한다 — 먼저 등록한 것은 그대로다', async () => {\n const first = name('first')\n const later = name('later')\n const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) })\n const resolver = createFigureResolver(server.fetch)\n\n await resolver([first])\n await resolver([first, later])\n\n expect(server.asked).to.deep.equal([[first], [later]])\n expect(hasFigure(first)).to.equal(true)\n expect(hasFigure(later)).to.equal(true)\n })\n\n it('씬의 자리에 앉아 동작한다 — 받은 것은 그려지고 나머지는 자리표시로 남는다', async () => {\n const onBoard = name('on-board')\n const never = name('never')\n const { fetch } = serverOf({ [onBoard]: figureAssetOf(onBoard) })\n const takeBack = registerTypeResolver(createFigureResolver(fetch) as never)\n\n try {\n const model = {\n type: 'container',\n components: [\n { type: onBoard, left: 0, top: 0, width: 100, height: 100 },\n { type: never, left: 0, top: 0, width: 100, height: 100 }\n ]\n }\n const resolution = await resolveUnknownTypes(model)\n expect(resolution.unresolved).to.deep.equal([never])\n expect(resolution.failed).to.deep.equal([])\n expect(hasFigure(onBoard)).to.equal(true)\n } finally {\n takeBack()\n }\n })\n})\n"]}