@mawaru/sdk 0.16.0 → 0.18.0
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/_schemas/extract-component.ts +2 -1
- package/_schemas/graph.test.ts +14 -2
- package/_schemas/graph.ts +19 -7
- package/_schemas/hook-manifest.ts +1 -1
- package/_schemas/node.ts +3 -3
- package/_schemas/port-spec.test.ts +75 -1
- package/_schemas/port-spec.ts +51 -20
- package/_schemas/program-manifest.ts +1 -1
- package/dist/_schemas/extract-component.js +2 -1
- package/dist/_schemas/graph.d.ts +23 -0
- package/dist/_schemas/graph.js +19 -7
- package/dist/_schemas/hook-manifest.js +1 -1
- package/dist/_schemas/node.js +3 -3
- package/dist/_schemas/port-spec.d.ts +1 -0
- package/dist/_schemas/port-spec.js +46 -15
- package/dist/_schemas/program-manifest.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GraphConnection, GraphNode } from "./graph.js"
|
|
2
2
|
import type { GraphPort } from "./node.js"
|
|
3
|
+
import { schemaTypeKey } from "./port-spec.js"
|
|
3
4
|
|
|
4
5
|
// 矩形選択からのグループ化・コンポーネント化(ノードのグループ化・コンポーネント化.md/
|
|
5
6
|
// コンポーネントの複数入口・複数出口.md)。
|
|
@@ -67,7 +68,7 @@ export const analyzeExtractSelection = (
|
|
|
67
68
|
// 同じ思想を境界にも適用する(グループ化=入口の合流の宣言)
|
|
68
69
|
const entryPortIds = [...new Set(inbound.map((c) => c.to_port_id))]
|
|
69
70
|
const entrySchemas = new Set(
|
|
70
|
-
entryPortIds.map((id) =>
|
|
71
|
+
entryPortIds.map((id) => schemaTypeKey(portById.get(id)?.schema ?? {})),
|
|
71
72
|
)
|
|
72
73
|
if (entrySchemas.size > 1) {
|
|
73
74
|
return {
|
package/_schemas/graph.test.ts
CHANGED
|
@@ -312,7 +312,7 @@ describe("saveGraphBodySchema", () => {
|
|
|
312
312
|
})
|
|
313
313
|
|
|
314
314
|
// fan-in(同じ in ポートへの流入2本以上)は同じ型でなければならない
|
|
315
|
-
// (docs/tasks/
|
|
315
|
+
// (docs/tasks/done/ポートスキーマのany撲滅.md)
|
|
316
316
|
const programNode = (outSchema: GraphPort["schema"]) => ({
|
|
317
317
|
...makeNode([
|
|
318
318
|
port("in", "in"),
|
|
@@ -333,6 +333,18 @@ describe("saveGraphBodySchema", () => {
|
|
|
333
333
|
).not.toThrow()
|
|
334
334
|
})
|
|
335
335
|
|
|
336
|
+
it("fan-in:注釈(title)だけが違う流入は同じ型として受け付ける", () => {
|
|
337
|
+
const a = programNode({ type: "string", title: "回答" })
|
|
338
|
+
const b = programNode({ type: "string", title: "要開発者確認" })
|
|
339
|
+
const sink = { ...makeNode(simplePorts()), kind: "wait" as const }
|
|
340
|
+
expect(() =>
|
|
341
|
+
saveGraphBodySchema.parse({
|
|
342
|
+
nodes: [a, b, sink],
|
|
343
|
+
connections: [connect(a, sink), connect(b, sink)],
|
|
344
|
+
}),
|
|
345
|
+
).not.toThrow()
|
|
346
|
+
})
|
|
347
|
+
|
|
336
348
|
it("fan-in:異なる具体型の流入を拒否する", () => {
|
|
337
349
|
const a = programNode({ type: "string" })
|
|
338
350
|
const b = programNode({ type: "number" })
|
|
@@ -623,7 +635,7 @@ describe("saveGraphBodySchema(kind 別ポート構成)", () => {
|
|
|
623
635
|
describe("saveGraphBodySchema(型の接続バリデーション)", () => {
|
|
624
636
|
// 接続の可否は kind ではなく型で決まる。reject / instruction の型固定は廃止され、
|
|
625
637
|
// ai の in はどの口も接続鏡映(接続元の型に同期)なので接続は自明に通る
|
|
626
|
-
// → docs/tasks/
|
|
638
|
+
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
627
639
|
it("human の reject → ai の instruction を許可(kind の縛りも型固定も無い)", () => {
|
|
628
640
|
const human = humanNode()
|
|
629
641
|
const ai = aiNode()
|
package/_schemas/graph.ts
CHANGED
|
@@ -99,7 +99,7 @@ export type WaitConfig = z.infer<typeof waitConfigSchema>
|
|
|
99
99
|
// Program ノードの kind 別設定(node_programs に対応)。実行先の repo はノード設定でなく
|
|
100
100
|
// installation から `${account_login}/${GITHUB_REPO_NAME}` に固定(backend)。program_dir は nodes/program/ 直下のディレクトリ名
|
|
101
101
|
// (例: sendReply。日本語可。エントリは main.ts 固定。リポジトリ規約 v2 は
|
|
102
|
-
// docs/tasks/
|
|
102
|
+
// docs/tasks/done/プログラムのリポジトリ規約v2(ディレクトリ+config.json).md)。
|
|
103
103
|
// ref 省略時はデフォルトブランチ
|
|
104
104
|
export const programConfigSchema = z.object({
|
|
105
105
|
program_dir: z
|
|
@@ -130,7 +130,7 @@ export type AiConfig = z.infer<typeof aiConfigSchema>
|
|
|
130
130
|
|
|
131
131
|
// 担当ユニットのメンバー:user_id の名指し、または動的な担当「ループを実行した人」
|
|
132
132
|
// (run の起動者。実行時に runs.start_by で解決し、解決できなければその1人だけ落ちる。
|
|
133
|
-
// docs/tasks/
|
|
133
|
+
// docs/tasks/done/Human担当に「ループを実行した人」を追加する.md)
|
|
134
134
|
export const RUN_STARTER = "run_starter"
|
|
135
135
|
export const assignMemberSchema = z.union([z.uuid(), z.literal(RUN_STARTER)])
|
|
136
136
|
export type AssignMember = z.infer<typeof assignMemberSchema>
|
|
@@ -198,7 +198,7 @@ export type HumanConfig = z.infer<typeof humanConfigSchema>
|
|
|
198
198
|
|
|
199
199
|
// コンポーネントノードの kind 別設定(node_components に対応)。呼ぶ先は
|
|
200
200
|
// kind='component' の loop。同 tenant・kind の検証と自 loop 参照の禁止は保存 API 側で行う
|
|
201
|
-
// (docs/tasks/
|
|
201
|
+
// (docs/tasks/done/コンポーネント(ループの部品化と再利用).md)
|
|
202
202
|
export const componentConfigSchema = z.object({
|
|
203
203
|
component_loop_id: z.uuid(),
|
|
204
204
|
// 配置の設定値:子 loop の refs 定義(default/required)への上書き。
|
|
@@ -211,7 +211,7 @@ export type ComponentConfig = z.infer<typeof componentConfigSchema>
|
|
|
211
211
|
// ノードにアタッチされた IOHook(node_hooks に対応。アタッチできるのは program ノードのみ)。
|
|
212
212
|
// on_input / on_output / on_signal はアタッチ時に repo の config.json(hookManifestSchema)から
|
|
213
213
|
// エディタが焼き込むスナップショット。ref 省略時はデフォルトブランチ
|
|
214
|
-
// (docs/tasks/
|
|
214
|
+
// (docs/tasks/done/NodeIOHooks(ノードへのIOフックのアタッチ).md・
|
|
215
215
|
// NodeIOHooksのonSignal(外部シグナルでのフック発火).md)
|
|
216
216
|
export const graphNodeHookSchema = z.object({
|
|
217
217
|
hook_dir: z
|
|
@@ -231,7 +231,7 @@ export type GraphNodeHook = z.infer<typeof graphNodeHookSchema>
|
|
|
231
231
|
|
|
232
232
|
// 新規ノードの id・ポートの id もクライアントが採番する(一時IDの解決を不要にする)
|
|
233
233
|
// ノードの表示アイコン:プリセット・絵文字・画像 URL のいずれか一つ。
|
|
234
|
-
// 未設定(省略)=既定の kind アイコン(docs/tasks/
|
|
234
|
+
// 未設定(省略)=既定の kind アイコン(docs/tasks/done/ノードのアイコン(プログラム任意設定・HUMAN担当者の自動表示).md)。
|
|
235
235
|
// preset は契約上不透明な文字列(承認ビューと同じ方式):カタログはフロントが
|
|
236
236
|
// 表示時に参照し、無い key は既定グリフへフォールバックする
|
|
237
237
|
export const nodeIconSchema = z.union([
|
|
@@ -249,7 +249,14 @@ export const NODE_ICON_PRESETS = [
|
|
|
249
249
|
{ key: "gmail", name: "Gmail" },
|
|
250
250
|
{ key: "google-sheets", name: "Google スプレッドシート" },
|
|
251
251
|
{ key: "google-drive", name: "Google Drive" },
|
|
252
|
+
{ key: "google-analytics", name: "Google Analytics" },
|
|
252
253
|
{ key: "notion", name: "Notion" },
|
|
254
|
+
{ key: "salesforce", name: "Salesforce" },
|
|
255
|
+
{ key: "freee", name: "freee" },
|
|
256
|
+
{ key: "airtable", name: "Airtable" },
|
|
257
|
+
{ key: "stripe", name: "Stripe" },
|
|
258
|
+
{ key: "hubspot", name: "HubSpot" },
|
|
259
|
+
{ key: "sentry", name: "Sentry" },
|
|
253
260
|
] as const
|
|
254
261
|
export type NodeIconPresetKey = (typeof NODE_ICON_PRESETS)[number]["key"]
|
|
255
262
|
|
|
@@ -263,6 +270,11 @@ export const graphNodeSchema = z.object({
|
|
|
263
270
|
position_y: z.number(),
|
|
264
271
|
// 表示アイコン(全 kind 共通の任意設定。human はフロントが担当から導出するので通常設定しない)
|
|
265
272
|
icon: nodeIconSchema.optional(),
|
|
273
|
+
// Slack ビュー(全 kind 共通の任意設定。承認待ち・完了メッセージの見せ方)。
|
|
274
|
+
// human の view と同じ「不透明なキー」で、実在・可視性の検証は保存時に mawaru 側が行う
|
|
275
|
+
// (カタログは非公開。@mawaru/common の slack-view.ts)。省略時は従来表示。
|
|
276
|
+
// ポートの型には一切影響しない
|
|
277
|
+
slack_view: z.string().min(1).optional(),
|
|
266
278
|
// kind 別設定(自 kind のときだけ意味を持つ。省略時は保存側がデフォルトを補う)
|
|
267
279
|
wait: waitConfigSchema.optional(),
|
|
268
280
|
program: programConfigSchema.optional(),
|
|
@@ -455,7 +467,7 @@ export const saveGraphBodySchema = z
|
|
|
455
467
|
|
|
456
468
|
// fan-in(同じ in ポートへの流入が2本以上)は同じ型でなければならない。
|
|
457
469
|
// 型が割れていると伝播が未宣言に落ちて下流の型チェックが効かなくなる
|
|
458
|
-
// (docs/tasks/
|
|
470
|
+
// (docs/tasks/done/ポートスキーマのany撲滅.md)
|
|
459
471
|
const inboundByPort = new Map<string, string[]>()
|
|
460
472
|
for (const conn of graph.connections) {
|
|
461
473
|
const list = inboundByPort.get(conn.to_port_id) ?? []
|
|
@@ -502,7 +514,7 @@ export const saveGraphBodySchema = z
|
|
|
502
514
|
|
|
503
515
|
// 差し戻し(reject)と instruction の組み合わせに kind の縛りも型固定も無い:
|
|
504
516
|
// reject の型はビューの宣言、instruction は ai の in の1つ(接続鏡映)でしかない
|
|
505
|
-
// → docs/tasks/
|
|
517
|
+
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
506
518
|
|
|
507
519
|
// 型検証は kind によらず常に 伝播型(out) ⊆ 伝播型(in)。
|
|
508
520
|
// 未宣言は unknown=判断できないので通す(作りかけのグラフを保存できなくしない)。
|
|
@@ -3,7 +3,7 @@ import { z } from "zod"
|
|
|
3
3
|
// nodes/hook/<dir>/config.json(NodeIOHooks)。フック自身が発火イベント(on)と
|
|
4
4
|
// 必要 env を宣言し、エディタがアタッチ時に読んで node_hooks へ焼き込む
|
|
5
5
|
// (programManifestSchema と同型の「repo が正・編集時に取り込む」方式。
|
|
6
|
-
// docs/tasks/
|
|
6
|
+
// docs/tasks/done/NodeIOHooks(ノードへのIOフックのアタッチ).md)。
|
|
7
7
|
// フックは任意のノードに付くため inputs / outputs のスキーマ宣言は持たない(unknown を受ける)。
|
|
8
8
|
// signal は外部シグナル(signals API)での発火(NodeIOHooksのonSignal(外部シグナルでのフック発火).md)
|
|
9
9
|
export const hookEventSchema = z.enum(["input", "output", "signal"])
|
package/_schemas/node.ts
CHANGED
|
@@ -7,11 +7,11 @@ export const nodeKindSchema = z.enum([
|
|
|
7
7
|
"guardrail",
|
|
8
8
|
"wait",
|
|
9
9
|
// 開始・終了ノード(恒等パススルー。run は必ず start から始まり end で終わる。
|
|
10
|
-
// docs/tasks/
|
|
10
|
+
// docs/tasks/done/開始・終了ノードの定義.md)
|
|
11
11
|
"start",
|
|
12
12
|
"end",
|
|
13
13
|
// コンポーネント(kind='component' の loop を配置して呼ぶ。子 run・戻り値あり。
|
|
14
|
-
// docs/tasks/
|
|
14
|
+
// docs/tasks/done/コンポーネント(ループの部品化と再利用).md)
|
|
15
15
|
"component",
|
|
16
16
|
])
|
|
17
17
|
export type NodeKind = z.infer<typeof nodeKindSchema>
|
|
@@ -39,7 +39,7 @@ export const portSideSchema = z.enum(["top", "right", "bottom", "left"])
|
|
|
39
39
|
export type PortSide = z.infer<typeof portSideSchema>
|
|
40
40
|
|
|
41
41
|
// グラフ保存契約のポート(ports テーブルの1行。id はクライアント採番。
|
|
42
|
-
// docs/tasks/
|
|
42
|
+
// docs/tasks/done/ポートのテーブル化.md)
|
|
43
43
|
export const graphPortSchema = z
|
|
44
44
|
.object({
|
|
45
45
|
id: z.uuid(),
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
propagateDerivedPorts,
|
|
14
14
|
resolvePortSchemas,
|
|
15
15
|
schemaFit,
|
|
16
|
+
schemaTypeKey,
|
|
16
17
|
UNDECLARED_SCHEMA,
|
|
17
18
|
} from "./port-spec.js"
|
|
18
19
|
|
|
@@ -140,7 +141,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
140
141
|
|
|
141
142
|
// human の in / approve / reject はビューの宣言をそのまま転写する(上流の kind は見ない)。
|
|
142
143
|
// sdk はカタログを知らないので、呼び出し元が viewPortSchemasOf で解決して渡す
|
|
143
|
-
// → docs/tasks/
|
|
144
|
+
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
144
145
|
it("ai → human 直結(コピー宣言):in も approve も上流の out と同型、reject はビューの宣言型", () => {
|
|
145
146
|
const aiOut = port("out", "main", declaredOut)
|
|
146
147
|
const ai = {
|
|
@@ -888,4 +889,77 @@ describe("mergeInboundSchemas / hasInboundConflict(fan-in)", () => {
|
|
|
888
889
|
)
|
|
889
890
|
expect(hasInboundConflict([])).toBe(false)
|
|
890
891
|
})
|
|
892
|
+
|
|
893
|
+
it("注釈(title / description)だけが違う流入は同じ型として合流できる", () => {
|
|
894
|
+
const answer = {
|
|
895
|
+
type: "object",
|
|
896
|
+
required: ["answer"],
|
|
897
|
+
properties: { answer: { type: "string", description: "顧客への回答" } },
|
|
898
|
+
} as const
|
|
899
|
+
const titled = { ...answer, title: "回答" }
|
|
900
|
+
const otherTitle = {
|
|
901
|
+
type: "object",
|
|
902
|
+
title: "要開発者確認",
|
|
903
|
+
required: ["answer"],
|
|
904
|
+
properties: { answer: { type: "string", description: "回答ドラフト" } },
|
|
905
|
+
} as const
|
|
906
|
+
|
|
907
|
+
expect(hasInboundConflict([titled, otherTitle])).toBe(false)
|
|
908
|
+
expect(mergeInboundSchemas([titled, otherTitle])).not.toEqual(
|
|
909
|
+
UNDECLARED_SCHEMA,
|
|
910
|
+
)
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
it("キー順・required の並びが違うだけの流入は同じ型として合流できる", () => {
|
|
914
|
+
const a = {
|
|
915
|
+
type: "object",
|
|
916
|
+
required: ["answer", "reasoning"],
|
|
917
|
+
properties: { answer: { type: "string" }, reasoning: { type: "string" } },
|
|
918
|
+
} as const
|
|
919
|
+
const b = {
|
|
920
|
+
properties: { reasoning: { type: "string" }, answer: { type: "string" } },
|
|
921
|
+
required: ["reasoning", "answer"],
|
|
922
|
+
type: "object",
|
|
923
|
+
} as const
|
|
924
|
+
|
|
925
|
+
expect(hasInboundConflict([a, b])).toBe(false)
|
|
926
|
+
expect(mergeInboundSchemas([a, b])).not.toEqual(UNDECLARED_SCHEMA)
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
it("合流の採用値は流入の順番に依らず決まる(保存ごとに注釈が揺れない)", () => {
|
|
930
|
+
const a = { type: "object", title: "あ", properties: {} } as const
|
|
931
|
+
const b = { type: "object", title: "い", properties: {} } as const
|
|
932
|
+
expect(mergeInboundSchemas([a, b])).toEqual(mergeInboundSchemas([b, a]))
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
it("構造が違えば注釈を無視しても衝突のまま", () => {
|
|
936
|
+
const a = { type: "object", properties: { x: { type: "string" } } } as const
|
|
937
|
+
const b = { type: "object", properties: { x: { type: "number" } } } as const
|
|
938
|
+
expect(hasInboundConflict([a, b])).toBe(true)
|
|
939
|
+
expect(mergeInboundSchemas([a, b])).toEqual(UNDECLARED_SCHEMA)
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
it("file マーカーは型の一部(注釈として落とさない)", () => {
|
|
943
|
+
expect(hasInboundConflict([fileSchema(), { type: "object" }])).toBe(true)
|
|
944
|
+
})
|
|
945
|
+
})
|
|
946
|
+
|
|
947
|
+
describe("schemaTypeKey(型としての同一性)", () => {
|
|
948
|
+
it("注釈だけの違いは同じキーになる", () => {
|
|
949
|
+
expect(schemaTypeKey({ type: "string", title: "回答" })).toBe(
|
|
950
|
+
schemaTypeKey({ type: "string", description: "根拠", title: "報告" }),
|
|
951
|
+
)
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
it("default は挙動(フォームの初期値)なので型の一部として残す", () => {
|
|
955
|
+
expect(schemaTypeKey({ type: "string", default: "a" })).not.toBe(
|
|
956
|
+
schemaTypeKey({ type: "string" }),
|
|
957
|
+
)
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
it("配列の要素順は保つ(items のタプルは意味を持つ)", () => {
|
|
961
|
+
expect(
|
|
962
|
+
schemaTypeKey({ type: "array", items: [{ type: "string" }] }),
|
|
963
|
+
).not.toBe(schemaTypeKey({ type: "array", items: [{ type: "number" }] }))
|
|
964
|
+
})
|
|
891
965
|
})
|
package/_schemas/port-spec.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
2
|
import type { GraphPort, JsonSchema, PortIo } from "./node.js"
|
|
3
3
|
|
|
4
|
-
// ポートの kind 別型システム(docs/tasks/
|
|
4
|
+
// ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
|
|
5
5
|
// 型の決まり方は2分類:宣言(program / ai の in・out。human はビューの宣言を転写)/
|
|
6
6
|
// 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
|
|
7
7
|
// mawaru がコードで型を固定するポートは無い
|
|
8
|
-
// (docs/tasks/
|
|
8
|
+
// (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
9
9
|
|
|
10
10
|
// 「まだ型が決まっていない」=未宣言を {} で表す(未接続・上流が未宣言・fan-in の型割れ)。
|
|
11
11
|
// 「何でも受ける」型は存在しない:program / ai の in は接続元の具体型を鏡映する
|
|
12
|
-
// (docs/tasks/
|
|
12
|
+
// (docs/tasks/done/ポートスキーマのany撲滅.md)
|
|
13
13
|
export const UNDECLARED_SCHEMA: JsonSchema = {}
|
|
14
14
|
|
|
15
15
|
export const isUndeclaredSchema = (schema: JsonSchema): boolean =>
|
|
@@ -20,7 +20,7 @@ export const isUndeclaredSchema = (schema: JsonSchema): boolean =>
|
|
|
20
20
|
// ではなく storage path の参照で、署名 URL はどこにも焼き込まない。
|
|
21
21
|
// **file の位置は名前ではなくこのマーカーで決まる**:runner が宣言スキーマと値を並行走査して
|
|
22
22
|
// 入力はワークスペースへ materialize(パスに置換)、出力はアップロード(参照に置換)する
|
|
23
|
-
// (docs/tasks/
|
|
23
|
+
// (docs/tasks/done/ポートスキーマ編集でファイル型を選べるようにする.md)。
|
|
24
24
|
// ajv は strict:false で未知キーワードを無視するので検証は素の JSON Schema のまま。
|
|
25
25
|
export const FILE_SCHEMA_MARKER = "x-mawaru-type"
|
|
26
26
|
export const FILE_SCHEMA_TYPE = "file"
|
|
@@ -236,29 +236,60 @@ export const kindPortsViolation = (
|
|
|
236
236
|
// 宣言 out(program / ai)は触らない。
|
|
237
237
|
// program / ai の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
|
|
238
238
|
// 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
|
|
239
|
-
// config.json へも書き戻される。docs/tasks/
|
|
239
|
+
// config.json へも書き戻される。docs/tasks/done/ポート編集のrepo書き戻し同期.md)
|
|
240
|
+
|
|
241
|
+
// ---- 型としての同一性(schemaTypeKey) ----
|
|
242
|
+
// 「同じ型か」を判定する唯一の物差し。合流(fan-in)・end の in の畳み込み・
|
|
243
|
+
// グループ化の入口・エディタの着地判定が全部これを使う。
|
|
244
|
+
// 素の JSON 等値だと、意味の同じ型が **注釈の差** や **キーの並び順** だけで別型に
|
|
245
|
+
// 見えて合流できなくなる(古い取り込みで ports.schema に残った title が典型)。
|
|
246
|
+
// 型=構造なので、人間向けの注釈は落とし、順序に意味の無い集合は並べ直して比べる。
|
|
247
|
+
// default は「フォームの初期値」という挙動なので型の一部として残す。
|
|
248
|
+
// x-mawaru-type(file の nominal マーカー)も当然残す
|
|
249
|
+
const ANNOTATION_KEYWORDS = new Set([
|
|
250
|
+
"title",
|
|
251
|
+
"description",
|
|
252
|
+
"$comment",
|
|
253
|
+
"examples",
|
|
254
|
+
"deprecated",
|
|
255
|
+
])
|
|
256
|
+
|
|
257
|
+
// required は集合(並びに意味が無い)。properties のキー順は表示順として意味を
|
|
258
|
+
// 持ちうるが、型の同一性には効かせない
|
|
259
|
+
const canonicalize = (value: unknown, key?: string): unknown => {
|
|
260
|
+
if (Array.isArray(value)) {
|
|
261
|
+
const items = value.map((v) => canonicalize(v))
|
|
262
|
+
return key === "required" ? [...items].sort() : items
|
|
263
|
+
}
|
|
264
|
+
if (value === null || typeof value !== "object") return value
|
|
265
|
+
return Object.fromEntries(
|
|
266
|
+
Object.entries(value as Record<string, unknown>)
|
|
267
|
+
.filter(([k]) => !ANNOTATION_KEYWORDS.has(k))
|
|
268
|
+
.map(([k, v]) => [k, canonicalize(v, k)] as const)
|
|
269
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const schemaTypeKey = (schema: JsonSchema): string =>
|
|
274
|
+
JSON.stringify(canonicalize(schema))
|
|
240
275
|
|
|
241
276
|
// 複数の流入(fan-in)から型を1つに決める:未宣言を除いて具体型が1種類ならその型、
|
|
242
277
|
// 0種類なら未宣言。2種類以上は型が割れているので未宣言に落とす
|
|
243
|
-
// (保存時に graph.ts が 422
|
|
278
|
+
// (保存時に graph.ts が 422 で弾く。伝播は止めずに固定点へ収束させる)。
|
|
279
|
+
// 注釈だけが違う流入は同じ型なので、採用する1つを JSON 等値で安定に選ぶ
|
|
280
|
+
// (接続の並び順で title が揺れて repo 書き戻しが空コミットを打たないように)
|
|
244
281
|
export const mergeInboundSchemas = (schemas: JsonSchema[]): JsonSchema => {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
),
|
|
251
|
-
]
|
|
252
|
-
return uniq.length === 1 && uniq[0] !== undefined
|
|
253
|
-
? (JSON.parse(uniq[0]) as JsonSchema)
|
|
254
|
-
: UNDECLARED_SCHEMA
|
|
282
|
+
const declared = schemas.filter((s) => !isUndeclaredSchema(s))
|
|
283
|
+
if (new Set(declared.map(schemaTypeKey)).size !== 1) return UNDECLARED_SCHEMA
|
|
284
|
+
return declared
|
|
285
|
+
.map((s) => [JSON.stringify(s), s] as const)
|
|
286
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))[0]?.[1] as JsonSchema
|
|
255
287
|
}
|
|
256
288
|
|
|
257
289
|
// 流入の具体型が2種類以上あるか(=同じポートに違う型を流し込んでいる)
|
|
258
290
|
export const hasInboundConflict = (schemas: JsonSchema[]): boolean =>
|
|
259
|
-
new Set(
|
|
260
|
-
|
|
261
|
-
).size > 1
|
|
291
|
+
new Set(schemas.filter((s) => !isUndeclaredSchema(s)).map(schemaTypeKey))
|
|
292
|
+
.size > 1
|
|
262
293
|
|
|
263
294
|
type PropagationNode = {
|
|
264
295
|
id: string
|
|
@@ -276,7 +307,7 @@ export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA
|
|
|
276
307
|
|
|
277
308
|
// 伝播のオプション。sdk は承認ビューのカタログを知らない(非公開。企業向けビューの
|
|
278
309
|
// キーが npm 公開物に載るのを避ける)ので、呼び出し元が解決して渡す
|
|
279
|
-
// → docs/tasks/
|
|
310
|
+
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
280
311
|
export type PropagationOptions<N> = {
|
|
281
312
|
// human の in / approve / reject に転写する型(承認ビューの宣言)。null / undefined
|
|
282
313
|
// ならその node は対象外(human 以外)。上流ノードの kind は一切見ない。
|
|
@@ -4,7 +4,7 @@ import { manifestInKeysViolation } from "./port-spec.js"
|
|
|
4
4
|
|
|
5
5
|
// nodes/program/<dir>/config.json(リポジトリ規約 v2)。プログラム自身が入出力スキーマを宣言し、
|
|
6
6
|
// エディタが取り込んで ports を自動生成する(実行時の正は ports テーブルのまま。
|
|
7
|
-
// docs/tasks/
|
|
7
|
+
// docs/tasks/done/プログラムのリポジトリ規約v2(ディレクトリ+config.json).md)。
|
|
8
8
|
// inputs / outputs は「ポート key → JSON Schema」のマップ。将来 join(in 複数)を導入しても
|
|
9
9
|
// 形式が変わらないよう最初から複数形にしてある
|
|
10
10
|
export const programManifestSchema = z
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { schemaTypeKey } from "./port-spec.js";
|
|
1
2
|
export const analyzeExtractSelection = (nodes, connections, selectedIds) => {
|
|
2
3
|
const selected = new Set(selectedIds);
|
|
3
4
|
// 1ノードからコンポーネント化できる(グループ化の「2つ以上」はエディタ側の
|
|
@@ -32,7 +33,7 @@ export const analyzeExtractSelection = (nodes, connections, selectedIds) => {
|
|
|
32
33
|
// 起点(どの外部ポートから入るか)は見ない。ノードが前のノードを知らないのと
|
|
33
34
|
// 同じ思想を境界にも適用する(グループ化=入口の合流の宣言)
|
|
34
35
|
const entryPortIds = [...new Set(inbound.map((c) => c.to_port_id))];
|
|
35
|
-
const entrySchemas = new Set(entryPortIds.map((id) =>
|
|
36
|
+
const entrySchemas = new Set(entryPortIds.map((id) => schemaTypeKey(portById.get(id)?.schema ?? {})));
|
|
36
37
|
if (entrySchemas.size > 1) {
|
|
37
38
|
return {
|
|
38
39
|
ok: false,
|
package/dist/_schemas/graph.d.ts
CHANGED
|
@@ -101,9 +101,30 @@ export declare const NODE_ICON_PRESETS: readonly [{
|
|
|
101
101
|
}, {
|
|
102
102
|
readonly key: "google-drive";
|
|
103
103
|
readonly name: "Google Drive";
|
|
104
|
+
}, {
|
|
105
|
+
readonly key: "google-analytics";
|
|
106
|
+
readonly name: "Google Analytics";
|
|
104
107
|
}, {
|
|
105
108
|
readonly key: "notion";
|
|
106
109
|
readonly name: "Notion";
|
|
110
|
+
}, {
|
|
111
|
+
readonly key: "salesforce";
|
|
112
|
+
readonly name: "Salesforce";
|
|
113
|
+
}, {
|
|
114
|
+
readonly key: "freee";
|
|
115
|
+
readonly name: "freee";
|
|
116
|
+
}, {
|
|
117
|
+
readonly key: "airtable";
|
|
118
|
+
readonly name: "Airtable";
|
|
119
|
+
}, {
|
|
120
|
+
readonly key: "stripe";
|
|
121
|
+
readonly name: "Stripe";
|
|
122
|
+
}, {
|
|
123
|
+
readonly key: "hubspot";
|
|
124
|
+
readonly name: "HubSpot";
|
|
125
|
+
}, {
|
|
126
|
+
readonly key: "sentry";
|
|
127
|
+
readonly name: "Sentry";
|
|
107
128
|
}];
|
|
108
129
|
export type NodeIconPresetKey = (typeof NODE_ICON_PRESETS)[number]["key"];
|
|
109
130
|
export declare const graphNodeSchema: z.ZodObject<{
|
|
@@ -152,6 +173,7 @@ export declare const graphNodeSchema: z.ZodObject<{
|
|
|
152
173
|
}, z.core.$strict>, z.ZodObject<{
|
|
153
174
|
image_url: z.ZodURL;
|
|
154
175
|
}, z.core.$strict>]>>;
|
|
176
|
+
slack_view: z.ZodOptional<z.ZodString>;
|
|
155
177
|
wait: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
156
178
|
wait_type: z.ZodLiteral<"duration">;
|
|
157
179
|
duration_seconds: z.ZodNumber;
|
|
@@ -251,6 +273,7 @@ export declare const saveGraphBodySchema: z.ZodObject<{
|
|
|
251
273
|
}, z.core.$strict>, z.ZodObject<{
|
|
252
274
|
image_url: z.ZodURL;
|
|
253
275
|
}, z.core.$strict>]>>;
|
|
276
|
+
slack_view: z.ZodOptional<z.ZodString>;
|
|
254
277
|
wait: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
255
278
|
wait_type: z.ZodLiteral<"duration">;
|
|
256
279
|
duration_seconds: z.ZodNumber;
|
package/dist/_schemas/graph.js
CHANGED
|
@@ -72,7 +72,7 @@ export const waitConfigSchema = z.discriminatedUnion("wait_type", [
|
|
|
72
72
|
// Program ノードの kind 別設定(node_programs に対応)。実行先の repo はノード設定でなく
|
|
73
73
|
// installation から `${account_login}/${GITHUB_REPO_NAME}` に固定(backend)。program_dir は nodes/program/ 直下のディレクトリ名
|
|
74
74
|
// (例: sendReply。日本語可。エントリは main.ts 固定。リポジトリ規約 v2 は
|
|
75
|
-
// docs/tasks/
|
|
75
|
+
// docs/tasks/done/プログラムのリポジトリ規約v2(ディレクトリ+config.json).md)。
|
|
76
76
|
// ref 省略時はデフォルトブランチ
|
|
77
77
|
export const programConfigSchema = z.object({
|
|
78
78
|
program_dir: z
|
|
@@ -93,7 +93,7 @@ export const aiConfigSchema = z.object({
|
|
|
93
93
|
});
|
|
94
94
|
// 担当ユニットのメンバー:user_id の名指し、または動的な担当「ループを実行した人」
|
|
95
95
|
// (run の起動者。実行時に runs.start_by で解決し、解決できなければその1人だけ落ちる。
|
|
96
|
-
// docs/tasks/
|
|
96
|
+
// docs/tasks/done/Human担当に「ループを実行した人」を追加する.md)
|
|
97
97
|
export const RUN_STARTER = "run_starter";
|
|
98
98
|
export const assignMemberSchema = z.union([z.uuid(), z.literal(RUN_STARTER)]);
|
|
99
99
|
// Human ノードの kind 別設定(node_humans + node_human_assignees に対応)。
|
|
@@ -153,7 +153,7 @@ export const humanConfigSchema = z.object({
|
|
|
153
153
|
});
|
|
154
154
|
// コンポーネントノードの kind 別設定(node_components に対応)。呼ぶ先は
|
|
155
155
|
// kind='component' の loop。同 tenant・kind の検証と自 loop 参照の禁止は保存 API 側で行う
|
|
156
|
-
// (docs/tasks/
|
|
156
|
+
// (docs/tasks/done/コンポーネント(ループの部品化と再利用).md)
|
|
157
157
|
export const componentConfigSchema = z.object({
|
|
158
158
|
component_loop_id: z.uuid(),
|
|
159
159
|
// 配置の設定値:子 loop の refs 定義(default/required)への上書き。
|
|
@@ -164,7 +164,7 @@ export const componentConfigSchema = z.object({
|
|
|
164
164
|
// ノードにアタッチされた IOHook(node_hooks に対応。アタッチできるのは program ノードのみ)。
|
|
165
165
|
// on_input / on_output / on_signal はアタッチ時に repo の config.json(hookManifestSchema)から
|
|
166
166
|
// エディタが焼き込むスナップショット。ref 省略時はデフォルトブランチ
|
|
167
|
-
// (docs/tasks/
|
|
167
|
+
// (docs/tasks/done/NodeIOHooks(ノードへのIOフックのアタッチ).md・
|
|
168
168
|
// NodeIOHooksのonSignal(外部シグナルでのフック発火).md)
|
|
169
169
|
export const graphNodeHookSchema = z.object({
|
|
170
170
|
hook_dir: z
|
|
@@ -179,7 +179,7 @@ export const graphNodeHookSchema = z.object({
|
|
|
179
179
|
});
|
|
180
180
|
// 新規ノードの id・ポートの id もクライアントが採番する(一時IDの解決を不要にする)
|
|
181
181
|
// ノードの表示アイコン:プリセット・絵文字・画像 URL のいずれか一つ。
|
|
182
|
-
// 未設定(省略)=既定の kind アイコン(docs/tasks/
|
|
182
|
+
// 未設定(省略)=既定の kind アイコン(docs/tasks/done/ノードのアイコン(プログラム任意設定・HUMAN担当者の自動表示).md)。
|
|
183
183
|
// preset は契約上不透明な文字列(承認ビューと同じ方式):カタログはフロントが
|
|
184
184
|
// 表示時に参照し、無い key は既定グリフへフォールバックする
|
|
185
185
|
export const nodeIconSchema = z.union([
|
|
@@ -195,7 +195,14 @@ export const NODE_ICON_PRESETS = [
|
|
|
195
195
|
{ key: "gmail", name: "Gmail" },
|
|
196
196
|
{ key: "google-sheets", name: "Google スプレッドシート" },
|
|
197
197
|
{ key: "google-drive", name: "Google Drive" },
|
|
198
|
+
{ key: "google-analytics", name: "Google Analytics" },
|
|
198
199
|
{ key: "notion", name: "Notion" },
|
|
200
|
+
{ key: "salesforce", name: "Salesforce" },
|
|
201
|
+
{ key: "freee", name: "freee" },
|
|
202
|
+
{ key: "airtable", name: "Airtable" },
|
|
203
|
+
{ key: "stripe", name: "Stripe" },
|
|
204
|
+
{ key: "hubspot", name: "HubSpot" },
|
|
205
|
+
{ key: "sentry", name: "Sentry" },
|
|
199
206
|
];
|
|
200
207
|
export const graphNodeSchema = z.object({
|
|
201
208
|
id: z.uuid(),
|
|
@@ -207,6 +214,11 @@ export const graphNodeSchema = z.object({
|
|
|
207
214
|
position_y: z.number(),
|
|
208
215
|
// 表示アイコン(全 kind 共通の任意設定。human はフロントが担当から導出するので通常設定しない)
|
|
209
216
|
icon: nodeIconSchema.optional(),
|
|
217
|
+
// Slack ビュー(全 kind 共通の任意設定。承認待ち・完了メッセージの見せ方)。
|
|
218
|
+
// human の view と同じ「不透明なキー」で、実在・可視性の検証は保存時に mawaru 側が行う
|
|
219
|
+
// (カタログは非公開。@mawaru/common の slack-view.ts)。省略時は従来表示。
|
|
220
|
+
// ポートの型には一切影響しない
|
|
221
|
+
slack_view: z.string().min(1).optional(),
|
|
210
222
|
// kind 別設定(自 kind のときだけ意味を持つ。省略時は保存側がデフォルトを補う)
|
|
211
223
|
wait: waitConfigSchema.optional(),
|
|
212
224
|
program: programConfigSchema.optional(),
|
|
@@ -382,7 +394,7 @@ export const saveGraphBodySchema = z
|
|
|
382
394
|
const resolved = resolvePortSchemas(graph.nodes, graph.connections);
|
|
383
395
|
// fan-in(同じ in ポートへの流入が2本以上)は同じ型でなければならない。
|
|
384
396
|
// 型が割れていると伝播が未宣言に落ちて下流の型チェックが効かなくなる
|
|
385
|
-
// (docs/tasks/
|
|
397
|
+
// (docs/tasks/done/ポートスキーマのany撲滅.md)
|
|
386
398
|
const inboundByPort = new Map();
|
|
387
399
|
for (const conn of graph.connections) {
|
|
388
400
|
const list = inboundByPort.get(conn.to_port_id) ?? [];
|
|
@@ -427,7 +439,7 @@ export const saveGraphBodySchema = z
|
|
|
427
439
|
}
|
|
428
440
|
// 差し戻し(reject)と instruction の組み合わせに kind の縛りも型固定も無い:
|
|
429
441
|
// reject の型はビューの宣言、instruction は ai の in の1つ(接続鏡映)でしかない
|
|
430
|
-
// → docs/tasks/
|
|
442
|
+
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
431
443
|
// 型検証は kind によらず常に 伝播型(out) ⊆ 伝播型(in)。
|
|
432
444
|
// 未宣言は unknown=判断できないので通す(作りかけのグラフを保存できなくしない)。
|
|
433
445
|
// 接続鏡映する口(program / ai の in)とコピー宣言のビューは接続元の型を採用する
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
// nodes/hook/<dir>/config.json(NodeIOHooks)。フック自身が発火イベント(on)と
|
|
3
3
|
// 必要 env を宣言し、エディタがアタッチ時に読んで node_hooks へ焼き込む
|
|
4
4
|
// (programManifestSchema と同型の「repo が正・編集時に取り込む」方式。
|
|
5
|
-
// docs/tasks/
|
|
5
|
+
// docs/tasks/done/NodeIOHooks(ノードへのIOフックのアタッチ).md)。
|
|
6
6
|
// フックは任意のノードに付くため inputs / outputs のスキーマ宣言は持たない(unknown を受ける)。
|
|
7
7
|
// signal は外部シグナル(signals API)での発火(NodeIOHooksのonSignal(外部シグナルでのフック発火).md)
|
|
8
8
|
export const hookEventSchema = z.enum(["input", "output", "signal"]);
|
package/dist/_schemas/node.js
CHANGED
|
@@ -6,11 +6,11 @@ export const nodeKindSchema = z.enum([
|
|
|
6
6
|
"guardrail",
|
|
7
7
|
"wait",
|
|
8
8
|
// 開始・終了ノード(恒等パススルー。run は必ず start から始まり end で終わる。
|
|
9
|
-
// docs/tasks/
|
|
9
|
+
// docs/tasks/done/開始・終了ノードの定義.md)
|
|
10
10
|
"start",
|
|
11
11
|
"end",
|
|
12
12
|
// コンポーネント(kind='component' の loop を配置して呼ぶ。子 run・戻り値あり。
|
|
13
|
-
// docs/tasks/
|
|
13
|
+
// docs/tasks/done/コンポーネント(ループの部品化と再利用).md)
|
|
14
14
|
"component",
|
|
15
15
|
]);
|
|
16
16
|
// ポート key(input/output 用の参照キー)の規則(db.md §2)。
|
|
@@ -28,7 +28,7 @@ export const portIoSchema = z.enum(["in", "out"]);
|
|
|
28
28
|
// 未指定は自動配置(先頭=右辺中央・以降=下辺等間隔)
|
|
29
29
|
export const portSideSchema = z.enum(["top", "right", "bottom", "left"]);
|
|
30
30
|
// グラフ保存契約のポート(ports テーブルの1行。id はクライアント採番。
|
|
31
|
-
// docs/tasks/
|
|
31
|
+
// docs/tasks/done/ポートのテーブル化.md)
|
|
32
32
|
export const graphPortSchema = z
|
|
33
33
|
.object({
|
|
34
34
|
id: z.uuid(),
|
|
@@ -47,6 +47,7 @@ export declare const dataFormatOf: (schema: JsonSchema) => DataFormat | null;
|
|
|
47
47
|
export declare const fixedInKeysOf: (kind: string) => string[] | null;
|
|
48
48
|
export declare const manifestInKeysViolation: (kind: string, keys: string[]) => string | null;
|
|
49
49
|
export declare const kindPortsViolation: (kind: string, ports: Pick<GraphPort, "io" | "key">[]) => string | null;
|
|
50
|
+
export declare const schemaTypeKey: (schema: JsonSchema) => string;
|
|
50
51
|
export declare const mergeInboundSchemas: (schemas: JsonSchema[]) => JsonSchema;
|
|
51
52
|
export declare const hasInboundConflict: (schemas: JsonSchema[]) => boolean;
|
|
52
53
|
type PropagationNode = {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
// ポートの kind 別型システム(docs/tasks/
|
|
2
|
+
// ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
|
|
3
3
|
// 型の決まり方は2分類:宣言(program / ai の in・out。human はビューの宣言を転写)/
|
|
4
4
|
// 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
|
|
5
5
|
// mawaru がコードで型を固定するポートは無い
|
|
6
|
-
// (docs/tasks/
|
|
6
|
+
// (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
7
7
|
// 「まだ型が決まっていない」=未宣言を {} で表す(未接続・上流が未宣言・fan-in の型割れ)。
|
|
8
8
|
// 「何でも受ける」型は存在しない:program / ai の in は接続元の具体型を鏡映する
|
|
9
|
-
// (docs/tasks/
|
|
9
|
+
// (docs/tasks/done/ポートスキーマのany撲滅.md)
|
|
10
10
|
export const UNDECLARED_SCHEMA = {};
|
|
11
11
|
export const isUndeclaredSchema = (schema) => Object.keys(schema).length === 0 || schema.type === undefined;
|
|
12
12
|
// ---- file 型(docs/tasks/done/AIノードのファイル入力(file型と添付).md) ----
|
|
@@ -14,7 +14,7 @@ export const isUndeclaredSchema = (schema) => Object.keys(schema).length === 0 |
|
|
|
14
14
|
// ではなく storage path の参照で、署名 URL はどこにも焼き込まない。
|
|
15
15
|
// **file の位置は名前ではなくこのマーカーで決まる**:runner が宣言スキーマと値を並行走査して
|
|
16
16
|
// 入力はワークスペースへ materialize(パスに置換)、出力はアップロード(参照に置換)する
|
|
17
|
-
// (docs/tasks/
|
|
17
|
+
// (docs/tasks/done/ポートスキーマ編集でファイル型を選べるようにする.md)。
|
|
18
18
|
// ajv は strict:false で未知キーワードを無視するので検証は素の JSON Schema のまま。
|
|
19
19
|
export const FILE_SCHEMA_MARKER = "x-mawaru-type";
|
|
20
20
|
export const FILE_SCHEMA_TYPE = "file";
|
|
@@ -199,22 +199,53 @@ export const kindPortsViolation = (kind, ports) => {
|
|
|
199
199
|
// 宣言 out(program / ai)は触らない。
|
|
200
200
|
// program / ai の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
|
|
201
201
|
// 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
|
|
202
|
-
// config.json へも書き戻される。docs/tasks/
|
|
202
|
+
// config.json へも書き戻される。docs/tasks/done/ポート編集のrepo書き戻し同期.md)
|
|
203
|
+
// ---- 型としての同一性(schemaTypeKey) ----
|
|
204
|
+
// 「同じ型か」を判定する唯一の物差し。合流(fan-in)・end の in の畳み込み・
|
|
205
|
+
// グループ化の入口・エディタの着地判定が全部これを使う。
|
|
206
|
+
// 素の JSON 等値だと、意味の同じ型が **注釈の差** や **キーの並び順** だけで別型に
|
|
207
|
+
// 見えて合流できなくなる(古い取り込みで ports.schema に残った title が典型)。
|
|
208
|
+
// 型=構造なので、人間向けの注釈は落とし、順序に意味の無い集合は並べ直して比べる。
|
|
209
|
+
// default は「フォームの初期値」という挙動なので型の一部として残す。
|
|
210
|
+
// x-mawaru-type(file の nominal マーカー)も当然残す
|
|
211
|
+
const ANNOTATION_KEYWORDS = new Set([
|
|
212
|
+
"title",
|
|
213
|
+
"description",
|
|
214
|
+
"$comment",
|
|
215
|
+
"examples",
|
|
216
|
+
"deprecated",
|
|
217
|
+
]);
|
|
218
|
+
// required は集合(並びに意味が無い)。properties のキー順は表示順として意味を
|
|
219
|
+
// 持ちうるが、型の同一性には効かせない
|
|
220
|
+
const canonicalize = (value, key) => {
|
|
221
|
+
if (Array.isArray(value)) {
|
|
222
|
+
const items = value.map((v) => canonicalize(v));
|
|
223
|
+
return key === "required" ? [...items].sort() : items;
|
|
224
|
+
}
|
|
225
|
+
if (value === null || typeof value !== "object")
|
|
226
|
+
return value;
|
|
227
|
+
return Object.fromEntries(Object.entries(value)
|
|
228
|
+
.filter(([k]) => !ANNOTATION_KEYWORDS.has(k))
|
|
229
|
+
.map(([k, v]) => [k, canonicalize(v, k)])
|
|
230
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
231
|
+
};
|
|
232
|
+
export const schemaTypeKey = (schema) => JSON.stringify(canonicalize(schema));
|
|
203
233
|
// 複数の流入(fan-in)から型を1つに決める:未宣言を除いて具体型が1種類ならその型、
|
|
204
234
|
// 0種類なら未宣言。2種類以上は型が割れているので未宣言に落とす
|
|
205
|
-
// (保存時に graph.ts が 422
|
|
235
|
+
// (保存時に graph.ts が 422 で弾く。伝播は止めずに固定点へ収束させる)。
|
|
236
|
+
// 注釈だけが違う流入は同じ型なので、採用する1つを JSON 等値で安定に選ぶ
|
|
237
|
+
// (接続の並び順で title が揺れて repo 書き戻しが空コミットを打たないように)
|
|
206
238
|
export const mergeInboundSchemas = (schemas) => {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
? JSON.parse(uniq[0])
|
|
214
|
-
: UNDECLARED_SCHEMA;
|
|
239
|
+
const declared = schemas.filter((s) => !isUndeclaredSchema(s));
|
|
240
|
+
if (new Set(declared.map(schemaTypeKey)).size !== 1)
|
|
241
|
+
return UNDECLARED_SCHEMA;
|
|
242
|
+
return declared
|
|
243
|
+
.map((s) => [JSON.stringify(s), s])
|
|
244
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))[0]?.[1];
|
|
215
245
|
};
|
|
216
246
|
// 流入の具体型が2種類以上あるか(=同じポートに違う型を流し込んでいる)
|
|
217
|
-
export const hasInboundConflict = (schemas) => new Set(schemas.filter((s) => !isUndeclaredSchema(s)).map(
|
|
247
|
+
export const hasInboundConflict = (schemas) => new Set(schemas.filter((s) => !isUndeclaredSchema(s)).map(schemaTypeKey))
|
|
248
|
+
.size > 1;
|
|
218
249
|
// 「上流の out をそのままコピーする」(in)/「in と同型」(approve)を表す指示。
|
|
219
250
|
// 承認ビューのカタログ(@mawaru/common)の COPY_UPSTREAM と同じ値
|
|
220
251
|
export const COPY_UPSTREAM_SCHEMA = "T";
|
|
@@ -3,7 +3,7 @@ import { jsonSchemaSchema, portKeySchema } from "./node.js";
|
|
|
3
3
|
import { manifestInKeysViolation } from "./port-spec.js";
|
|
4
4
|
// nodes/program/<dir>/config.json(リポジトリ規約 v2)。プログラム自身が入出力スキーマを宣言し、
|
|
5
5
|
// エディタが取り込んで ports を自動生成する(実行時の正は ports テーブルのまま。
|
|
6
|
-
// docs/tasks/
|
|
6
|
+
// docs/tasks/done/プログラムのリポジトリ規約v2(ディレクトリ+config.json).md)。
|
|
7
7
|
// inputs / outputs は「ポート key → JSON Schema」のマップ。将来 join(in 複数)を導入しても
|
|
8
8
|
// 形式が変わらないよう最初から複数形にしてある
|
|
9
9
|
export const programManifestSchema = z
|