@things-factory/headless-twin 10.1.37 → 10.1.38
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/engine/twin-engine.js +3 -2
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/twin-state-channel.d.ts +14 -0
- package/dist-server/engine/twin-state-channel.js +31 -0
- package/dist-server/engine/twin-state-channel.js.map +1 -0
- package/dist-server/service/reference/reference-progress-channel.d.ts +14 -0
- package/dist-server/service/reference/reference-progress-channel.js +24 -0
- package/dist-server/service/reference/reference-progress-channel.js.map +1 -0
- package/dist-server/service/reference/reference-progress-subscription.d.ts +2 -3
- package/dist-server/service/reference/reference-progress-subscription.js +14 -6
- package/dist-server/service/reference/reference-progress-subscription.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.js +3 -6
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +10 -1
- package/dist-server/service/twin-forecast/gap-analytics.js +15 -6
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.d.ts +2 -3
- package/dist-server/service/twin-state/twin-state-subscription.js +20 -13
- package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
- package/package.json +6 -6
- package/server/engine/twin-engine.ts +6 -5
- package/server/engine/twin-state-channel.ts +39 -0
- package/server/service/reference/reference-progress-channel.ts +26 -0
- package/server/service/reference/reference-progress-subscription.ts +15 -10
- package/server/service/reference/reference-resolver.ts +6 -6
- package/server/service/twin-forecast/gap-analytics.ts +15 -6
- package/server/service/twin-forecast/twin-forecast-query.ts +1 -1
- package/server/service/twin-state/twin-state-subscription.ts +22 -19
- package/test/boot-resume.test.ts +4 -1
- package/test/gap-analytics.test.ts +25 -1
- package/test/open-doors.test.ts +197 -0
- package/test/twin-state-channel.test.ts +111 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const TWIN_STATE_TOPIC = "twin-state";
|
|
2
|
+
export interface TwinStateMessageShape {
|
|
3
|
+
instanceId: string;
|
|
4
|
+
kind: string;
|
|
5
|
+
revision?: number;
|
|
6
|
+
payload?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface TwinStatePayload {
|
|
9
|
+
domainId: string;
|
|
10
|
+
twinState: TwinStateMessageShape;
|
|
11
|
+
}
|
|
12
|
+
export declare function twinStatePayload(domainId: string, twinState: TwinStateMessageShape): TwinStatePayload;
|
|
13
|
+
/** Passes only this tenant's messages about this twin. */
|
|
14
|
+
export declare function twinStateFilter(domainId: string, instanceId: string): (p: TwinStatePayload | undefined) => boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The `twin-state` channel — **a message says whose twin it is, and the filter asks.** Pure.
|
|
4
|
+
*
|
|
5
|
+
* ── What went wrong (2026-09-24) ─────────────────────────────────────────────
|
|
6
|
+
* A twin is `(domain, instanceId)` (`runtime-key.ts`), and the same instanceId in two tenants is the
|
|
7
|
+
* expected case — reference source names such as `sap-ewm-1710` are vocabulary tenants share. The
|
|
8
|
+
* channel had not caught up: publishers sent `{ twinState: { instanceId, … } }` with no domain, and the
|
|
9
|
+
* subscription filtered on instanceId alone. Ownership was checked once, when subscribing. So a tenant
|
|
10
|
+
* watching its own `sap-ewm-1710` also received the other tenant's deltas, and the snapshot sent to a
|
|
11
|
+
* new subscriber went to every subscriber of that id in every tenant.
|
|
12
|
+
*
|
|
13
|
+
* The rule is shell's `data` channel's (the message carries its domain, the filter compares it with the
|
|
14
|
+
* key), compared by id because the twin's key already is. Every sender builds its payload here, and
|
|
15
|
+
* `twin-state-channel.test.ts` fails if a sender builds one anywhere else.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.TWIN_STATE_TOPIC = void 0;
|
|
19
|
+
exports.twinStatePayload = twinStatePayload;
|
|
20
|
+
exports.twinStateFilter = twinStateFilter;
|
|
21
|
+
exports.TWIN_STATE_TOPIC = 'twin-state';
|
|
22
|
+
function twinStatePayload(domainId, twinState) {
|
|
23
|
+
if (!domainId)
|
|
24
|
+
throw new Error('twinStatePayload: domainId is required — a twin state message always belongs to a tenant');
|
|
25
|
+
return { domainId, twinState };
|
|
26
|
+
}
|
|
27
|
+
/** Passes only this tenant's messages about this twin. */
|
|
28
|
+
function twinStateFilter(domainId, instanceId) {
|
|
29
|
+
return (p) => !!p && p.domainId === domainId && p.twinState?.instanceId === instanceId;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=twin-state-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twin-state-channel.js","sourceRoot":"","sources":["../../server/engine/twin-state-channel.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAgBH,4CAGC;AAGD,0CAEC;AAtBY,QAAA,gBAAgB,GAAG,YAAY,CAAA;AAc5C,SAAgB,gBAAgB,CAAC,QAAgB,EAAE,SAAgC;IACjF,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,0FAA0F,CAAC,CAAA;IAC1H,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAA;AAChC,CAAC;AAED,0DAA0D;AAC1D,SAAgB,eAAe,CAAC,QAAgB,EAAE,UAAkB;IAClE,OAAO,CAAC,CAA+B,EAAW,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,KAAK,UAAU,CAAA;AAC/H,CAAC","sourcesContent":["/*\n * The `twin-state` channel — **a message says whose twin it is, and the filter asks.** Pure.\n *\n * ── What went wrong (2026-09-24) ─────────────────────────────────────────────\n * A twin is `(domain, instanceId)` (`runtime-key.ts`), and the same instanceId in two tenants is the\n * expected case — reference source names such as `sap-ewm-1710` are vocabulary tenants share. The\n * channel had not caught up: publishers sent `{ twinState: { instanceId, … } }` with no domain, and the\n * subscription filtered on instanceId alone. Ownership was checked once, when subscribing. So a tenant\n * watching its own `sap-ewm-1710` also received the other tenant's deltas, and the snapshot sent to a\n * new subscriber went to every subscriber of that id in every tenant.\n *\n * The rule is shell's `data` channel's (the message carries its domain, the filter compares it with the\n * key), compared by id because the twin's key already is. Every sender builds its payload here, and\n * `twin-state-channel.test.ts` fails if a sender builds one anywhere else.\n */\n\nexport const TWIN_STATE_TOPIC = 'twin-state'\n\nexport interface TwinStateMessageShape {\n instanceId: string\n kind: string\n revision?: number\n payload?: unknown\n}\n\nexport interface TwinStatePayload {\n domainId: string\n twinState: TwinStateMessageShape\n}\n\nexport function twinStatePayload(domainId: string, twinState: TwinStateMessageShape): TwinStatePayload {\n if (!domainId) throw new Error('twinStatePayload: domainId is required — a twin state message always belongs to a tenant')\n return { domainId, twinState }\n}\n\n/** Passes only this tenant's messages about this twin. */\nexport function twinStateFilter(domainId: string, instanceId: string) {\n return (p: TwinStatePayload | undefined): boolean => !!p && p.domainId === domainId && p.twinState?.instanceId === instanceId\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const REFERENCE_PROGRESS_TOPIC = "twin-reference-progress";
|
|
2
|
+
export interface ReferenceProgressPayload<M extends {
|
|
3
|
+
source: string;
|
|
4
|
+
} = {
|
|
5
|
+
source: string;
|
|
6
|
+
}> {
|
|
7
|
+
domainId: string;
|
|
8
|
+
twinReferenceProgress: M;
|
|
9
|
+
}
|
|
10
|
+
export declare function referenceProgressPayload<M extends {
|
|
11
|
+
source: string;
|
|
12
|
+
}>(domainId: string, message: M): ReferenceProgressPayload<M>;
|
|
13
|
+
/** Passes only this tenant's progress for this source. */
|
|
14
|
+
export declare function referenceProgressFilter(domainId: string, source: string): (p: ReferenceProgressPayload | undefined) => boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The `twin-reference-progress` channel — **progress says whose import it is, and the filter asks.** Pure.
|
|
4
|
+
*
|
|
5
|
+
* The subscription filtered on `source` alone and checked only that the caller had some domain, so
|
|
6
|
+
* anyone who knew a source name — and names like `sap-ewm-1710` are shared between tenants — received
|
|
7
|
+
* another tenant's import progress, including its failure text. Same fix and same rule as
|
|
8
|
+
* `engine/twin-state-channel.ts`.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.REFERENCE_PROGRESS_TOPIC = void 0;
|
|
12
|
+
exports.referenceProgressPayload = referenceProgressPayload;
|
|
13
|
+
exports.referenceProgressFilter = referenceProgressFilter;
|
|
14
|
+
exports.REFERENCE_PROGRESS_TOPIC = 'twin-reference-progress';
|
|
15
|
+
function referenceProgressPayload(domainId, message) {
|
|
16
|
+
if (!domainId)
|
|
17
|
+
throw new Error('referenceProgressPayload: domainId is required — import progress always belongs to a tenant');
|
|
18
|
+
return { domainId, twinReferenceProgress: message };
|
|
19
|
+
}
|
|
20
|
+
/** Passes only this tenant's progress for this source. */
|
|
21
|
+
function referenceProgressFilter(domainId, source) {
|
|
22
|
+
return (p) => !!p && p.domainId === domainId && p.twinReferenceProgress?.source === source;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=reference-progress-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-progress-channel.js","sourceRoot":"","sources":["../../../server/service/reference/reference-progress-channel.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AASH,4DAGC;AAGD,0DAGC;AAhBY,QAAA,wBAAwB,GAAG,yBAAyB,CAAA;AAOjE,SAAgB,wBAAwB,CAA+B,QAAgB,EAAE,OAAU;IACjG,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,6FAA6F,CAAC,CAAA;IAC7H,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,OAAO,EAAE,CAAA;AACrD,CAAC;AAED,0DAA0D;AAC1D,SAAgB,uBAAuB,CAAC,QAAgB,EAAE,MAAc;IACtE,OAAO,CAAC,CAAuC,EAAW,EAAE,CAC1D,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,qBAAqB,EAAE,MAAM,KAAK,MAAM,CAAA;AAChF,CAAC","sourcesContent":["/*\n * The `twin-reference-progress` channel — **progress says whose import it is, and the filter asks.** Pure.\n *\n * The subscription filtered on `source` alone and checked only that the caller had some domain, so\n * anyone who knew a source name — and names like `sap-ewm-1710` are shared between tenants — received\n * another tenant's import progress, including its failure text. Same fix and same rule as\n * `engine/twin-state-channel.ts`.\n */\n\nexport const REFERENCE_PROGRESS_TOPIC = 'twin-reference-progress'\n\nexport interface ReferenceProgressPayload<M extends { source: string } = { source: string }> {\n domainId: string\n twinReferenceProgress: M\n}\n\nexport function referenceProgressPayload<M extends { source: string }>(domainId: string, message: M): ReferenceProgressPayload<M> {\n if (!domainId) throw new Error('referenceProgressPayload: domainId is required — import progress always belongs to a tenant')\n return { domainId, twinReferenceProgress: message }\n}\n\n/** Passes only this tenant's progress for this source. */\nexport function referenceProgressFilter(domainId: string, source: string) {\n return (p: ReferenceProgressPayload | undefined): boolean =>\n !!p && p.domainId === domainId && p.twinReferenceProgress?.source === source\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReferenceProgressPayload } from './reference-progress-channel.js';
|
|
1
2
|
export declare class TwinReferenceProgressMessage {
|
|
2
3
|
source: string;
|
|
3
4
|
siteId: string;
|
|
@@ -11,7 +12,5 @@ export declare class TwinReferenceProgressMessage {
|
|
|
11
12
|
error?: string;
|
|
12
13
|
}
|
|
13
14
|
export declare class TwinReferenceProgressSubscription {
|
|
14
|
-
twinReferenceProgress(payload:
|
|
15
|
-
twinReferenceProgress: TwinReferenceProgressMessage;
|
|
16
|
-
}, source: string): TwinReferenceProgressMessage;
|
|
15
|
+
twinReferenceProgress(payload: ReferenceProgressPayload<TwinReferenceProgressMessage>, source: string): TwinReferenceProgressMessage;
|
|
17
16
|
}
|
|
@@ -15,6 +15,8 @@ const type_graphql_1 = require("type-graphql");
|
|
|
15
15
|
const shell_1 = require("@things-factory/shell");
|
|
16
16
|
const graphql_yoga_1 = require("graphql-yoga");
|
|
17
17
|
const reference_progress_js_1 = require("./reference-progress.js");
|
|
18
|
+
const reference_progress_channel_js_1 = require("./reference-progress-channel.js");
|
|
19
|
+
const twin_reference_js_1 = require("./twin-reference.js");
|
|
18
20
|
let TwinReferenceProgressMessage = class TwinReferenceProgressMessage {
|
|
19
21
|
};
|
|
20
22
|
exports.TwinReferenceProgressMessage = TwinReferenceProgressMessage;
|
|
@@ -72,13 +74,19 @@ let TwinReferenceProgressSubscription = class TwinReferenceProgressSubscription
|
|
|
72
74
|
exports.TwinReferenceProgressSubscription = TwinReferenceProgressSubscription;
|
|
73
75
|
tslib_1.__decorate([
|
|
74
76
|
(0, type_graphql_1.Subscription)(returns => TwinReferenceProgressMessage, {
|
|
75
|
-
|
|
77
|
+
/*
|
|
78
|
+
* Tenancy is asked twice (2026-09-24): the source must be one of the caller's references, and every
|
|
79
|
+
* message must be the caller's domain's. It used to filter on `source` alone after checking only that
|
|
80
|
+
* the caller had a domain — and source names are shared between tenants.
|
|
81
|
+
*/
|
|
82
|
+
subscribe: async ({ args, context }) => {
|
|
76
83
|
const { source } = args;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
const { domain, user } = context?.state ?? {};
|
|
85
|
+
await (0, shell_1.assertDomainSubscribeAllowed)(domain, user, (d, u) => process.superUserGranted(d, u));
|
|
86
|
+
const owned = await (0, shell_1.getRepository)(twin_reference_js_1.TwinReference).findOne({ where: { domain: { id: domain.id }, source } });
|
|
87
|
+
if (!owned)
|
|
88
|
+
throw new Error(`reference not found in this tenant: ${source}`);
|
|
89
|
+
return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe(reference_progress_channel_js_1.REFERENCE_PROGRESS_TOPIC), (0, graphql_yoga_1.filter)((0, reference_progress_channel_js_1.referenceProgressFilter)(domain.id, source)));
|
|
82
90
|
},
|
|
83
91
|
description: 'Progress while importing a reference — standard step names, so one translation per step covers every connector.'
|
|
84
92
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference-progress-subscription.js","sourceRoot":"","sources":["../../../server/service/reference/reference-progress-subscription.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;GAQG;AACH,+CAAwF;AACxF,
|
|
1
|
+
{"version":3,"file":"reference-progress-subscription.js","sourceRoot":"","sources":["../../../server/service/reference/reference-progress-subscription.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 유입 진행 구독 — **트윈을 만드는 동안 무엇을 하고 있는지 화면이 안다.**\n *\n * 마스터 읽기는 원본에 여러 질의를 보내는 긴 작업이고, 그 사이 화면은 아무것도 알 수 없었다. 단계를\n * 아는 것은 어댑터뿐이므로 어댑터가 **표준 낱말**로 말하고(`REFERENCE_STEPS`) 호스트가 자기 두 단계를\n * 더해 이 채널로 흘린다.\n *\n * 낱말이 표준이므로 화면은 **단계마다 번역 하나**만 두면 모든 커넥터를 보여 준다.\n */\nimport { Arg, Field, Int, ObjectType, Resolver, Root, Subscription } from 'type-graphql'\nimport { assertDomainSubscribeAllowed, getRepository, pubsub } from '@things-factory/shell'\nimport { filter, pipe } from 'graphql-yoga'\n\nimport { REFERENCE_STEPS } from './reference-progress.js'\nimport { REFERENCE_PROGRESS_TOPIC, referenceProgressFilter, type ReferenceProgressPayload } from './reference-progress-channel.js'\nimport { TwinReference } from './twin-reference.js'\n\n@ObjectType({ description: 'One step of progress while a reference is being imported into twins.' })\nexport class TwinReferenceProgressMessage {\n @Field({ description: 'Which reference (source) this belongs to.' })\n source: string\n\n @Field({ description: 'Which site is being built.' })\n siteId: string\n\n @Field({ description: `Standard step name — one of: ${REFERENCE_STEPS.join(' | ')}.` })\n step: string\n\n @Field(type => Int, { description: 'Steps finished for this site.' })\n done: number\n\n @Field(type => Int, {\n nullable: true,\n description: 'Total steps for this site. Absent when the adapter did not declare its steps — the UI then shows no percentage rather than inventing one.'\n })\n total?: number\n\n @Field(type => Int, { description: 'Index of the site being built (0-based).' })\n siteIndex: number\n\n @Field(type => Int, { description: 'How many sites are being built.' })\n siteCount: number\n\n @Field({ nullable: true, description: 'A fact within the step, such as how many rows were read.' })\n detail?: string\n\n @Field({ nullable: true, description: 'True when this step failed. The UI keeps the progress panel and marks this step.' })\n failed?: boolean\n\n @Field({ nullable: true, description: 'Why it failed. Present only on failure.' })\n error?: string\n}\n\n@Resolver()\nexport class TwinReferenceProgressSubscription {\n @Subscription(returns => TwinReferenceProgressMessage, {\n /*\n * Tenancy is asked twice (2026-09-24): the source must be one of the caller's references, and every\n * message must be the caller's domain's. It used to filter on `source` alone after checking only that\n * the caller had a domain — and source names are shared between tenants.\n */\n subscribe: async ({ args, context }) => {\n const { source } = args\n const { domain, user } = (context as any)?.state ?? {}\n await assertDomainSubscribeAllowed(domain, user, (d, u) => process.superUserGranted(d, u))\n const owned = await getRepository(TwinReference).findOne({ where: { domain: { id: domain.id }, source } })\n if (!owned) throw new Error(`reference not found in this tenant: ${source}`)\n return pipe(pubsub.subscribe(REFERENCE_PROGRESS_TOPIC), filter(referenceProgressFilter(domain.id, source)))\n },\n description: 'Progress while importing a reference — standard step names, so one translation per step covers every connector.'\n })\n twinReferenceProgress(\n @Root() payload: ReferenceProgressPayload<TwinReferenceProgressMessage>,\n @Arg('source') source: string\n ): TwinReferenceProgressMessage {\n return payload.twinReferenceProgress\n }\n}\n"]}
|
|
@@ -4,6 +4,7 @@ exports.TwinReferenceResolver = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const shell_1 = require("@things-factory/shell");
|
|
6
6
|
const reference_progress_js_1 = require("./reference-progress.js");
|
|
7
|
+
const reference_progress_channel_js_1 = require("./reference-progress-channel.js");
|
|
7
8
|
const reference_assessment_js_1 = require("./reference-assessment.js");
|
|
8
9
|
const type_graphql_1 = require("type-graphql");
|
|
9
10
|
const shell_2 = require("@things-factory/shell");
|
|
@@ -233,9 +234,7 @@ let TwinReferenceResolver = class TwinReferenceResolver {
|
|
|
233
234
|
return;
|
|
234
235
|
lastDone = p.done;
|
|
235
236
|
lastStep = p.step;
|
|
236
|
-
shell_1.pubsub.publish(
|
|
237
|
-
twinReferenceProgress: { ...p, siteId, siteIndex: 0, siteCount: 1, total: p.total ?? stepTotal, source: progressSource }
|
|
238
|
-
});
|
|
237
|
+
shell_1.pubsub.publish(reference_progress_channel_js_1.REFERENCE_PROGRESS_TOPIC, (0, reference_progress_channel_js_1.referenceProgressPayload)(domainId, { ...p, siteId, siteIndex: 0, siteCount: 1, total: p.total ?? stepTotal, source: progressSource }));
|
|
239
238
|
};
|
|
240
239
|
try {
|
|
241
240
|
const master = await this.masterFromOrigin(domainId, origin, instanceId, st => emit({ step: st.key, done: st.done, total: stepTotal, detail: st.detail }));
|
|
@@ -695,9 +694,7 @@ let TwinReferenceResolver = class TwinReferenceResolver {
|
|
|
695
694
|
*/
|
|
696
695
|
const declared = adapter.masterSteps?.length;
|
|
697
696
|
const stepTotal = declared === undefined ? undefined : declared + reference_progress_js_1.HOST_STEPS.length;
|
|
698
|
-
const emit = (p) => shell_1.pubsub.publish(
|
|
699
|
-
twinReferenceProgress: { ...p, siteCount: sites.length, total: p.total ?? stepTotal, source }
|
|
700
|
-
});
|
|
697
|
+
const emit = (p) => shell_1.pubsub.publish(reference_progress_channel_js_1.REFERENCE_PROGRESS_TOPIC, (0, reference_progress_channel_js_1.referenceProgressPayload)(domainId, { ...p, siteCount: sites.length, total: p.total ?? stepTotal, source }));
|
|
701
698
|
/*
|
|
702
699
|
* **읽은 뒤의 총평을 사이트마다 남긴다** (2026-08-22).
|
|
703
700
|
*
|