@nanobpm/urban 0.35.0 → 0.37.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/dist/cli.d.ts +0 -1
- package/dist/cli.js +32 -47
- package/dist/openapi/spec.d.ts +9 -0
- package/dist/openapi/spec.js +22 -0
- package/dist/runtime/core/modules/api.d.ts +19 -4
- package/dist/runtime/core/modules/api.js +81 -3
- package/dist/runtime/core/runtime.d.ts +10 -0
- package/dist/runtime/core/runtime.js +2 -2
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +2 -1
- package/dist/toolkit/api-dir.d.ts +11 -0
- package/dist/toolkit/api-dir.js +36 -0
- package/dist/toolkit/derivers/api.d.ts +25 -2
- package/dist/toolkit/derivers/api.js +60 -5
- package/dist/toolkit/gen.js +4 -1
- package/dist/toolkit/generate.d.ts +27 -0
- package/dist/toolkit/generate.js +47 -0
- package/dist/toolkit/index.d.ts +8 -1
- package/dist/toolkit/index.js +5 -1
- package/dist/toolkit/scaffold/operations.d.ts +21 -0
- package/dist/toolkit/scaffold/operations.js +55 -0
- package/dist/toolkit/scaffold/workers.d.ts +7 -5
- package/dist/toolkit/scaffold/workers.js +12 -5
- package/dist/toolkit/scaffold.d.ts +16 -0
- package/dist/toolkit/scaffold.js +41 -1
- package/package.json +74 -74
- package/src/cli.test.ts +0 -1
- package/src/cli.ts +30 -45
- package/src/openapi/spec.ts +24 -0
- package/src/runtime/core/modules/api.test.ts +104 -2
- package/src/runtime/core/modules/api.ts +95 -8
- package/src/runtime/core/runtime.test.ts +53 -1
- package/src/runtime/core/runtime.ts +12 -2
- package/src/runtime/index.ts +3 -1
- package/src/toolkit/api-dir.test.ts +32 -0
- package/src/toolkit/api-dir.ts +41 -0
- package/src/toolkit/derivers/api.test.ts +41 -3
- package/src/toolkit/derivers/api.ts +75 -5
- package/src/toolkit/gen.ts +5 -2
- package/src/toolkit/generate.test.ts +135 -0
- package/src/toolkit/generate.ts +71 -0
- package/src/toolkit/index.ts +10 -0
- package/src/toolkit/scaffold/operations.test.ts +136 -0
- package/src/toolkit/scaffold/operations.ts +75 -0
- package/src/toolkit/scaffold/workers.test.ts +35 -0
- package/src/toolkit/scaffold/workers.ts +13 -5
- package/src/toolkit/scaffold.ts +63 -1
package/dist/toolkit/gen.js
CHANGED
|
@@ -98,7 +98,10 @@ async function collectAll(opts) {
|
|
|
98
98
|
const specRef = typeof manifest.api?.spec === "string" ? manifest.api.spec.trim() : "";
|
|
99
99
|
if (!opts.modelsOnly && specRef.length > 0) {
|
|
100
100
|
const specText = await io.readText(join(root, specRef));
|
|
101
|
-
|
|
101
|
+
const opsDir = typeof manifest.api?.dir === "string" && manifest.api.dir.trim().length > 0
|
|
102
|
+
? manifest.api.dir.trim()
|
|
103
|
+
: undefined;
|
|
104
|
+
artifacts.push(...deriveApi(parseSpec(specText), opsDir));
|
|
102
105
|
}
|
|
103
106
|
return { artifacts: sortArtifacts(artifacts), derived };
|
|
104
107
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GenOptions, GenResult } from "./gen.ts";
|
|
2
|
+
import type { OperationStubOutcome, StubOutcome } from "./scaffold.ts";
|
|
3
|
+
import type { StubManifestEntry } from "./scaffold/workers.ts";
|
|
4
|
+
export interface GenerateResult extends GenResult {
|
|
5
|
+
/** Worker-stub scaffolding outcomes (created / kept / would-create). */
|
|
6
|
+
workerStubs: StubOutcome[];
|
|
7
|
+
/** Operation-delegate scaffolding outcomes (created / kept / would-create). */
|
|
8
|
+
operationStubs: OperationStubOutcome[];
|
|
9
|
+
/** Worker manifest entries wired into `workers[]` (write mode only). */
|
|
10
|
+
wiredWorkers: StubManifestEntry[];
|
|
11
|
+
/** True when `workers[]` was patched (write mode, unwired workers present). */
|
|
12
|
+
manifestPatched: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* App-relative paths of stubs that would be created — the scaffolding equivalent of `drift`.
|
|
15
|
+
* Populated in `check` mode (nothing is written); a caller should fail CI when non-empty.
|
|
16
|
+
*/
|
|
17
|
+
missingStubs: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Derive artifacts and scaffold the write-once handler stubs they reference, in one pass.
|
|
21
|
+
* In `check` mode nothing is written and any would-be-created stub is reported in `missingStubs`.
|
|
22
|
+
* Scaffolding is skipped when derivation is `incomplete` (never scaffold from a half-derived model);
|
|
23
|
+
* the caller reports the derivation failure first.
|
|
24
|
+
*/
|
|
25
|
+
export declare function generate(opts: GenOptions & {
|
|
26
|
+
check?: boolean;
|
|
27
|
+
}): Promise<GenerateResult>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// `urban gen`: the whole generation concern in one pass — derive artifacts (migrations, worker-I/O,
|
|
2
|
+
// code-first models, OpenAPI contracts + controller) AND scaffold the write-once handler stubs those
|
|
3
|
+
// artifacts reference (worker stubs from the model, operation-delegate stubs from the OpenAPI spec).
|
|
4
|
+
//
|
|
5
|
+
// This composes the pure derivation (`runGen`, gen.ts) with the write-once scaffolders (scaffold.ts)
|
|
6
|
+
// WITHOUT a cycle: scaffold.ts already depends on gen.ts, so this leaf module depends on both and
|
|
7
|
+
// neither depends back. Scaffolding is write-once (an existing, human-owned stub is never clobbered),
|
|
8
|
+
// so folding it into `gen` is idempotent. `--check` writes nothing: a missing stub surfaces in
|
|
9
|
+
// `missingStubs` exactly like a drifted artifact surfaces in `drift`, so CI fails when a stub the
|
|
10
|
+
// generated controller imports isn't committed.
|
|
11
|
+
import { runGen } from "./gen.js";
|
|
12
|
+
import { scaffoldOperations, scaffoldWorkers } from "./scaffold.js";
|
|
13
|
+
/**
|
|
14
|
+
* Derive artifacts and scaffold the write-once handler stubs they reference, in one pass.
|
|
15
|
+
* In `check` mode nothing is written and any would-be-created stub is reported in `missingStubs`.
|
|
16
|
+
* Scaffolding is skipped when derivation is `incomplete` (never scaffold from a half-derived model);
|
|
17
|
+
* the caller reports the derivation failure first.
|
|
18
|
+
*/
|
|
19
|
+
export async function generate(opts) {
|
|
20
|
+
const res = await runGen(opts);
|
|
21
|
+
const empty = {
|
|
22
|
+
...res,
|
|
23
|
+
workerStubs: [],
|
|
24
|
+
operationStubs: [],
|
|
25
|
+
wiredWorkers: [],
|
|
26
|
+
manifestPatched: false,
|
|
27
|
+
missingStubs: [],
|
|
28
|
+
};
|
|
29
|
+
if (res.incomplete)
|
|
30
|
+
return empty;
|
|
31
|
+
const write = !opts.check;
|
|
32
|
+
const scaffoldOpts = { root: opts.root, io: opts.io, manifestFile: opts.manifestFile, write };
|
|
33
|
+
const workers = await scaffoldWorkers(scaffoldOpts);
|
|
34
|
+
const operations = await scaffoldOperations(scaffoldOpts);
|
|
35
|
+
const missingStubs = [
|
|
36
|
+
...workers.outcomes.filter((o) => o.status === "would-create").map((o) => o.handlerPath),
|
|
37
|
+
...operations.outcomes.filter((o) => o.status === "would-create").map((o) => o.handlerPath),
|
|
38
|
+
];
|
|
39
|
+
return {
|
|
40
|
+
...res,
|
|
41
|
+
workerStubs: workers.outcomes,
|
|
42
|
+
operationStubs: operations.outcomes,
|
|
43
|
+
wiredWorkers: workers.wired,
|
|
44
|
+
manifestPatched: workers.manifestPatched,
|
|
45
|
+
missingStubs,
|
|
46
|
+
};
|
|
47
|
+
}
|
package/dist/toolkit/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { deriveMeta, emitMeta, foldMeta, metaDeriver, scanModelMeta, META_TS, }
|
|
|
13
13
|
export type { MetaDecl } from "./derivers/meta.ts";
|
|
14
14
|
export { deriveMessageBindings, emitMessageBindings, emitMessageBindingsRuntime, messagesDeriver, scanModelMessages, MESSAGE_BINDINGS_DTS, MESSAGE_BINDINGS_TS, } from "./derivers/messages.ts";
|
|
15
15
|
export type { MessageIo, MessageBindingDecl } from "./derivers/messages.ts";
|
|
16
|
-
export { apiDeriver, deriveApi, emitApiBindings, emitApiBindingsRuntime, schemaToTs, API_BINDINGS_DTS, API_BINDINGS_TS, } from "./derivers/api.ts";
|
|
16
|
+
export { apiDeriver, deriveApi, emitApiBindings, emitApiBindingsRuntime, emitApiController, schemaToTs, API_BINDINGS_DTS, API_BINDINGS_TS, API_CONTROLLER_TS, } from "./derivers/api.ts";
|
|
17
17
|
export { collectOperations, HTTP_METHODS, isSafeOperationId, operationsWithoutId, operationsWithUnsafeId, parseSpec, refName, resolveSchema, toRouteMatcher, undeclaredPathParams, validateValue, } from "../openapi/spec.ts";
|
|
18
18
|
export type { HttpMethodLower, OpenApiDoc, OpenApiParameter, OpenApiSchema, OperationInfo, } from "../openapi/spec.ts";
|
|
19
19
|
export declare const DERIVERS: readonly [import("./artifact.ts").Deriver<import("./derivers/migrations.ts").ToolkitManifest>, import("./artifact.ts").Deriver<import("./derivers/migrations.ts").ToolkitManifest>, import("./artifact.ts").Deriver<{
|
|
@@ -26,9 +26,12 @@ export declare const DERIVERS: readonly [import("./artifact.ts").Deriver<import(
|
|
|
26
26
|
declaredTypeIds: Iterable<string>;
|
|
27
27
|
}>, import("./artifact.ts").Deriver<{
|
|
28
28
|
doc: import("../openapi/spec.ts").OpenApiDoc;
|
|
29
|
+
operationsDir?: string;
|
|
29
30
|
}>];
|
|
30
31
|
export { collectArtifacts, runGen, joinPath, readModels, expandPattern, previewModels } from "./gen.ts";
|
|
31
32
|
export type { GenIO, GenOptions, GenResult } from "./gen.ts";
|
|
33
|
+
export { generate } from "./generate.ts";
|
|
34
|
+
export type { GenerateResult } from "./generate.ts";
|
|
32
35
|
export { createNodeGenIO } from "./fsio.ts";
|
|
33
36
|
export { deriveModels, isWorkflow, bpmnFilename, processesOutDir, DEFAULT_WORKFLOW_PATTERNS, PROCESSES_DIR, MODEL_PROVENANCE, } from "./models.ts";
|
|
34
37
|
export type { DerivedModels, DerivedModelInfo, ModelError, ModelsManifest } from "./models.ts";
|
|
@@ -36,5 +39,9 @@ export { planWorkerScaffold, renderWorkerStub, slugifyTaskType, } from "./scaffo
|
|
|
36
39
|
export type { ScaffoldWorker, SkippedWorker, SkipReason, StubManifestEntry, WorkerScaffoldPlan, WorkerStubPlan, } from "./scaffold/workers.ts";
|
|
37
40
|
export { scaffoldWorkers } from "./scaffold.ts";
|
|
38
41
|
export type { ScaffoldOptions, ScaffoldRun, StubOutcome, StubStatus } from "./scaffold.ts";
|
|
42
|
+
export { planOperationScaffold, renderOperationStub } from "./scaffold/operations.ts";
|
|
43
|
+
export type { OperationStubPlan } from "./scaffold/operations.ts";
|
|
44
|
+
export { scaffoldOperations } from "./scaffold.ts";
|
|
45
|
+
export type { OperationScaffoldRun, OperationStubOutcome } from "./scaffold.ts";
|
|
39
46
|
export { addConnector } from "./addConnector.ts";
|
|
40
47
|
export type { AddConnectorOptions, AddConnectorResult } from "./addConnector.ts";
|
package/dist/toolkit/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { deriveWorkerBindings, emitWorkerBindings, emitWorkerBindingsRuntime, ov
|
|
|
11
11
|
export { byModelPath, typeRefFor } from "./derivers/worker-io.js";
|
|
12
12
|
export { deriveMeta, emitMeta, foldMeta, metaDeriver, scanModelMeta, META_TS, } from "./derivers/meta.js";
|
|
13
13
|
export { deriveMessageBindings, emitMessageBindings, emitMessageBindingsRuntime, messagesDeriver, scanModelMessages, MESSAGE_BINDINGS_DTS, MESSAGE_BINDINGS_TS, } from "./derivers/messages.js";
|
|
14
|
-
export { apiDeriver, deriveApi, emitApiBindings, emitApiBindingsRuntime, schemaToTs, API_BINDINGS_DTS, API_BINDINGS_TS, } from "./derivers/api.js";
|
|
14
|
+
export { apiDeriver, deriveApi, emitApiBindings, emitApiBindingsRuntime, emitApiController, schemaToTs, API_BINDINGS_DTS, API_BINDINGS_TS, API_CONTROLLER_TS, } from "./derivers/api.js";
|
|
15
15
|
// The OpenAPI machinery the deriver + runtime share (ADR 0058).
|
|
16
16
|
export { collectOperations, HTTP_METHODS, isSafeOperationId, operationsWithoutId, operationsWithUnsafeId, parseSpec, refName, resolveSchema, toRouteMatcher, undeclaredPathParams, validateValue, } from "../openapi/spec.js";
|
|
17
17
|
// The registry of all derivers (for discovery / IDE migration).
|
|
@@ -31,10 +31,14 @@ export const DERIVERS = [
|
|
|
31
31
|
];
|
|
32
32
|
// Gen orchestrator + IO
|
|
33
33
|
export { collectArtifacts, runGen, joinPath, readModels, expandPattern, previewModels } from "./gen.js";
|
|
34
|
+
export { generate } from "./generate.js";
|
|
34
35
|
export { createNodeGenIO } from "./fsio.js";
|
|
35
36
|
// Code-first model derivation (workflows/*.ts → BPMN)
|
|
36
37
|
export { deriveModels, isWorkflow, bpmnFilename, processesOutDir, DEFAULT_WORKFLOW_PATTERNS, PROCESSES_DIR, MODEL_PROVENANCE, } from "./models.js";
|
|
37
38
|
// Worker-stub scaffolder (ADR 0056): write-once handler stubs from the model.
|
|
38
39
|
export { planWorkerScaffold, renderWorkerStub, slugifyTaskType, } from "./scaffold/workers.js";
|
|
39
40
|
export { scaffoldWorkers } from "./scaffold.js";
|
|
41
|
+
// Operation-delegate scaffolder (ADR 0059): write-once typed delegate stubs from the OpenAPI spec.
|
|
42
|
+
export { planOperationScaffold, renderOperationStub } from "./scaffold/operations.js";
|
|
43
|
+
export { scaffoldOperations } from "./scaffold.js";
|
|
40
44
|
export { addConnector } from "./addConnector.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DEFAULT_OPERATIONS_DIR } from "../api-dir.ts";
|
|
2
|
+
import { type OpenApiDoc } from "../../openapi/spec.ts";
|
|
3
|
+
export { DEFAULT_OPERATIONS_DIR };
|
|
4
|
+
/** One planned operation-delegate stub. */
|
|
5
|
+
export interface OperationStubPlan {
|
|
6
|
+
operationId: string;
|
|
7
|
+
/** App-relative delegate path: `<dir>/<operationId>.ts`. */
|
|
8
|
+
handlerPath: string;
|
|
9
|
+
/** The stub file contents (a typed `defineOperation` that throws `NotImplemented` → HTTP 501). */
|
|
10
|
+
stub: string;
|
|
11
|
+
}
|
|
12
|
+
/** Render a stub matching a real hand-authored operation delegate (default-exported, typed via the
|
|
13
|
+
* generated `defineOperation`). It throws `NotImplemented` so the endpoint returns 501 until the
|
|
14
|
+
* body is implemented — never a silent 500, never a drift from the contract. */
|
|
15
|
+
export declare function renderOperationStub(operationId: string, dir?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Plan write-once delegate stubs from the spec: one per declared `operationId` (collectOperations
|
|
18
|
+
* already skips ops without a safe id). The planner emits a plan for every operation; the impure
|
|
19
|
+
* edge (`scaffoldOperations`) keeps an existing file verbatim and creates only the absent ones.
|
|
20
|
+
*/
|
|
21
|
+
export declare function planOperationScaffold(doc: OpenApiDoc, dir?: string): OperationStubPlan[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Pure planner for the write-once operation-delegate scaffolder (ADR 0059). Given a parsed OpenAPI
|
|
2
|
+
// document, it plans one typed delegate stub per declared `operationId` — the ONE part of the HTTP
|
|
3
|
+
// surface that cannot be derived from the spec (the handler *body* is human logic). Everything here
|
|
4
|
+
// is pure and testable like a deriver; the impure write-if-absent edge lives in `../scaffold.ts`.
|
|
5
|
+
//
|
|
6
|
+
// scaffold ≠ derive: stubs are human-owned files under `<dir>/<operationId>.ts`, so they are planned
|
|
7
|
+
// once and never overwritten — unlike the derived `nano-generated/` tree (which includes the
|
|
8
|
+
// controller registry that statically imports these delegates and type-checks the whole set).
|
|
9
|
+
import { DEFAULT_OPERATIONS_DIR, normalizeApiDir } from "../api-dir.js";
|
|
10
|
+
import { assertUniqueOperationIds, collectOperations } from "../../openapi/spec.js";
|
|
11
|
+
export { DEFAULT_OPERATIONS_DIR };
|
|
12
|
+
/** Normalize an `api.dir` to a clean relative segment (see `normalizeApiDir`); rejects absolute
|
|
13
|
+
* paths and `..` segments so stubs are scaffolded where the runtime resolves delegates from. */
|
|
14
|
+
function normalizeDir(dir) {
|
|
15
|
+
return normalizeApiDir(dir);
|
|
16
|
+
}
|
|
17
|
+
/** Number of `../` hops from `<dir>/<file>.ts` back up to the app root (dir may be nested). */
|
|
18
|
+
function upToRoot(dir) {
|
|
19
|
+
const depth = dir.split("/").filter((s) => s.length > 0).length;
|
|
20
|
+
return "../".repeat(depth);
|
|
21
|
+
}
|
|
22
|
+
/** Render a stub matching a real hand-authored operation delegate (default-exported, typed via the
|
|
23
|
+
* generated `defineOperation`). It throws `NotImplemented` so the endpoint returns 501 until the
|
|
24
|
+
* body is implemented — never a silent 500, never a drift from the contract. */
|
|
25
|
+
export function renderOperationStub(operationId, dir = DEFAULT_OPERATIONS_DIR) {
|
|
26
|
+
const up = upToRoot(normalizeDir(dir));
|
|
27
|
+
const key = JSON.stringify(operationId);
|
|
28
|
+
return (`// Operation delegate stub for the \`${operationId}\` endpoint, scaffolded from the OpenAPI spec\n` +
|
|
29
|
+
`// (ADR 0059). This file is yours to edit — \`urban gen\` will never overwrite it. The\n` +
|
|
30
|
+
`// generated controller (nano-generated/controller.ts) imports this default export and\n` +
|
|
31
|
+
`// type-checks it against the spec, so the signature cannot drift. Implement the body below\n` +
|
|
32
|
+
`// (the validated \`{ req, params, query, body }\` input + the injected \`app\`) and delete the throw.\n` +
|
|
33
|
+
`import { NotImplemented } from "@nanobpm/urban";\n` +
|
|
34
|
+
`import { defineOperation } from ${JSON.stringify(`${up}nano-generated/operations.ts`)};\n` +
|
|
35
|
+
`\n` +
|
|
36
|
+
`export default defineOperation(${key}, async (input, app) => {\n` +
|
|
37
|
+
` // \`input\` is the validated request (params/query/body/req); reach app state + services\n` +
|
|
38
|
+
` // through the injected \`app\` API. Return \`{ status?, headers?, body? }\` (or nothing → 204).\n` +
|
|
39
|
+
` throw new NotImplemented(${key});\n` +
|
|
40
|
+
`});\n`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Plan write-once delegate stubs from the spec: one per declared `operationId` (collectOperations
|
|
44
|
+
* already skips ops without a safe id). The planner emits a plan for every operation; the impure
|
|
45
|
+
* edge (`scaffoldOperations`) keeps an existing file verbatim and creates only the absent ones.
|
|
46
|
+
*/
|
|
47
|
+
export function planOperationScaffold(doc, dir = DEFAULT_OPERATIONS_DIR) {
|
|
48
|
+
assertUniqueOperationIds(doc);
|
|
49
|
+
const cleanDir = normalizeDir(dir);
|
|
50
|
+
return collectOperations(doc).map((op) => ({
|
|
51
|
+
operationId: op.operationId,
|
|
52
|
+
handlerPath: `${cleanDir}/${op.operationId}.ts`,
|
|
53
|
+
stub: renderOperationStub(op.operationId, cleanDir),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
@@ -26,7 +26,7 @@ export interface WorkerStubPlan {
|
|
|
26
26
|
stub: string;
|
|
27
27
|
manifestEntry: StubManifestEntry;
|
|
28
28
|
}
|
|
29
|
-
export type SkipReason = "already-wired" | "orchestrator" | "duplicate";
|
|
29
|
+
export type SkipReason = "already-wired" | "orchestrator" | "duplicate" | "external";
|
|
30
30
|
export interface SkippedWorker {
|
|
31
31
|
taskType: string;
|
|
32
32
|
reason: SkipReason;
|
|
@@ -41,8 +41,10 @@ export declare function slugifyTaskType(taskType: string): string;
|
|
|
41
41
|
export declare function renderWorkerStub(taskType: string, typedIn: boolean, typedOut: boolean): string;
|
|
42
42
|
/**
|
|
43
43
|
* Plan write-once handler stubs from the models. A stub is planned for each service-task
|
|
44
|
-
* `taskType` that is NOT already wired in the manifest, NOT the imperative orchestrator,
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* `taskType` that is NOT already wired in the manifest, NOT the imperative orchestrator, NOT
|
|
45
|
+
* declared external (serviced by an out-of-process worker), and not a duplicate of one already
|
|
46
|
+
* planned. `declaredTypeIds` are the manifest `types` ids — an envelope `in`/`out` is typed only
|
|
47
|
+
* when it names one (matching the worker-io deriver). `externalTaskTypes` are the manifest's
|
|
48
|
+
* declared external task types, which the app owns the model for but deliberately does not host.
|
|
47
49
|
*/
|
|
48
|
-
export declare function planWorkerScaffold(models: ModelSource[], workers?: ScaffoldWorker[], declaredTypeIds?: Iterable<string>): WorkerScaffoldPlan;
|
|
50
|
+
export declare function planWorkerScaffold(models: ModelSource[], workers?: ScaffoldWorker[], declaredTypeIds?: Iterable<string>, externalTaskTypes?: Iterable<string>): WorkerScaffoldPlan;
|
|
@@ -41,7 +41,7 @@ export function renderWorkerStub(taskType, typedIn, typedOut) {
|
|
|
41
41
|
? ` // \`job.variables\` is typed as WorkerInputs[${key}].`
|
|
42
42
|
: ` // \`job.variables\` is an open Record<string, unknown> (no declared input type).`;
|
|
43
43
|
return (`// Handler stub for the \`${taskType}\` service task, scaffolded from the model (ADR 0056).\n` +
|
|
44
|
-
`// This file is yours to edit — \`urban
|
|
44
|
+
`// This file is yours to edit — \`urban gen\` will never overwrite it. Implement the body\n` +
|
|
45
45
|
`// below (use \`app.data.table(...)\` for state) and delete the throw.\n` +
|
|
46
46
|
`import type { AppJobHandler } from "@nanobpm/urban/worker";\n` +
|
|
47
47
|
generatedImport +
|
|
@@ -57,15 +57,18 @@ export function renderWorkerStub(taskType, typedIn, typedOut) {
|
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Plan write-once handler stubs from the models. A stub is planned for each service-task
|
|
60
|
-
* `taskType` that is NOT already wired in the manifest, NOT the imperative orchestrator,
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* `taskType` that is NOT already wired in the manifest, NOT the imperative orchestrator, NOT
|
|
61
|
+
* declared external (serviced by an out-of-process worker), and not a duplicate of one already
|
|
62
|
+
* planned. `declaredTypeIds` are the manifest `types` ids — an envelope `in`/`out` is typed only
|
|
63
|
+
* when it names one (matching the worker-io deriver). `externalTaskTypes` are the manifest's
|
|
64
|
+
* declared external task types, which the app owns the model for but deliberately does not host.
|
|
63
65
|
*/
|
|
64
|
-
export function planWorkerScaffold(models, workers = [], declaredTypeIds = []) {
|
|
66
|
+
export function planWorkerScaffold(models, workers = [], declaredTypeIds = [], externalTaskTypes = []) {
|
|
65
67
|
const wired = new Set(workers
|
|
66
68
|
.map((w) => w?.taskType)
|
|
67
69
|
.filter((t) => typeof t === "string" && t.length > 0));
|
|
68
70
|
const declared = new Set(declaredTypeIds);
|
|
71
|
+
const external = new Set(externalTaskTypes);
|
|
69
72
|
const plans = [];
|
|
70
73
|
const skipped = [];
|
|
71
74
|
const seenTaskTypes = new Set();
|
|
@@ -77,6 +80,10 @@ export function planWorkerScaffold(models, workers = [], declaredTypeIds = []) {
|
|
|
77
80
|
skipped.push({ taskType, reason: "orchestrator" });
|
|
78
81
|
continue;
|
|
79
82
|
}
|
|
83
|
+
if (external.has(taskType)) {
|
|
84
|
+
skipped.push({ taskType, reason: "external" });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
80
87
|
if (wired.has(taskType)) {
|
|
81
88
|
skipped.push({ taskType, reason: "already-wired" });
|
|
82
89
|
continue;
|
|
@@ -29,3 +29,19 @@ export interface ScaffoldRun {
|
|
|
29
29
|
}
|
|
30
30
|
/** Scaffold write-once worker stubs from the model, wiring them into `manifest.workers[]`. */
|
|
31
31
|
export declare function scaffoldWorkers(opts: ScaffoldOptions): Promise<ScaffoldRun>;
|
|
32
|
+
/** One planned operation-delegate outcome (write-once). */
|
|
33
|
+
export interface OperationStubOutcome {
|
|
34
|
+
operationId: string;
|
|
35
|
+
handlerPath: string;
|
|
36
|
+
status: StubStatus;
|
|
37
|
+
}
|
|
38
|
+
export interface OperationScaffoldRun {
|
|
39
|
+
outcomes: OperationStubOutcome[];
|
|
40
|
+
write: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Scaffold write-once operation-delegate stubs from the app's OpenAPI spec (ADR 0059). One typed
|
|
44
|
+
* `defineOperation` stub per declared `operationId`, created only if absent (human-owned files are
|
|
45
|
+
* kept verbatim). Dry-run by default. A no-op when the app declares no `api.spec`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function scaffoldOperations(opts: ScaffoldOptions): Promise<OperationScaffoldRun>;
|
package/dist/toolkit/scaffold.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// human-owned, so an existing file is KEPT verbatim, never clobbered (the opposite of `runGen`).
|
|
5
5
|
import { joinPath, readModels } from "./gen.js";
|
|
6
6
|
import { planWorkerScaffold, } from "./scaffold/workers.js";
|
|
7
|
+
import { planOperationScaffold } from "./scaffold/operations.js";
|
|
8
|
+
import { parseSpec } from "../openapi/spec.js";
|
|
7
9
|
/** Scaffold write-once worker stubs from the model, wiring them into `manifest.workers[]`. */
|
|
8
10
|
export async function scaffoldWorkers(opts) {
|
|
9
11
|
const { root, io } = opts;
|
|
@@ -13,7 +15,7 @@ export async function scaffoldWorkers(opts) {
|
|
|
13
15
|
const manifest = JSON.parse(await io.readText(manifestPath));
|
|
14
16
|
const models = await readModels(root, io, manifest);
|
|
15
17
|
const declaredTypeIds = Object.keys(manifest.types ?? {});
|
|
16
|
-
const { plans, skipped } = planWorkerScaffold(models, manifest.workers ?? [], declaredTypeIds);
|
|
18
|
+
const { plans, skipped } = planWorkerScaffold(models, manifest.workers ?? [], declaredTypeIds, manifest.externalTaskTypes ?? []);
|
|
17
19
|
const outcomes = [];
|
|
18
20
|
const wired = [];
|
|
19
21
|
for (const plan of plans) {
|
|
@@ -52,3 +54,41 @@ export async function scaffoldWorkers(opts) {
|
|
|
52
54
|
}
|
|
53
55
|
return { outcomes, skipped, wired, manifestPatched, write };
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Scaffold write-once operation-delegate stubs from the app's OpenAPI spec (ADR 0059). One typed
|
|
59
|
+
* `defineOperation` stub per declared `operationId`, created only if absent (human-owned files are
|
|
60
|
+
* kept verbatim). Dry-run by default. A no-op when the app declares no `api.spec`.
|
|
61
|
+
*/
|
|
62
|
+
export async function scaffoldOperations(opts) {
|
|
63
|
+
const { root, io } = opts;
|
|
64
|
+
const write = opts.write ?? false;
|
|
65
|
+
const manifestFile = opts.manifestFile ?? "nano.app.json";
|
|
66
|
+
const manifestPath = joinPath(root, manifestFile);
|
|
67
|
+
const manifest = JSON.parse(await io.readText(manifestPath));
|
|
68
|
+
const specRef = typeof manifest.api?.spec === "string" ? manifest.api.spec.trim() : "";
|
|
69
|
+
if (specRef.length === 0)
|
|
70
|
+
return { outcomes: [], write };
|
|
71
|
+
const specText = await io.readText(joinPath(root, specRef));
|
|
72
|
+
const dir = typeof manifest.api?.dir === "string" && manifest.api.dir.trim().length > 0
|
|
73
|
+
? manifest.api.dir.trim()
|
|
74
|
+
: undefined;
|
|
75
|
+
const plans = planOperationScaffold(parseSpec(specText), dir);
|
|
76
|
+
const outcomes = [];
|
|
77
|
+
for (const plan of plans) {
|
|
78
|
+
const abs = joinPath(root, plan.handlerPath);
|
|
79
|
+
const exists = await io.exists(abs);
|
|
80
|
+
let status;
|
|
81
|
+
if (exists) {
|
|
82
|
+
status = "kept"; // human-owned — never clobber
|
|
83
|
+
}
|
|
84
|
+
else if (write) {
|
|
85
|
+
await io.writeText(abs, plan.stub);
|
|
86
|
+
status = "created";
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
status = "would-create";
|
|
90
|
+
}
|
|
91
|
+
outcomes.push({ operationId: plan.operationId, handlerPath: plan.handlerPath, status });
|
|
92
|
+
}
|
|
93
|
+
return { outcomes, write };
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
2
|
+
"name": "@nanobpm/urban",
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nanobpm",
|
|
9
|
+
"urban",
|
|
10
|
+
"cli",
|
|
11
|
+
"runtime",
|
|
12
|
+
"codegen",
|
|
13
|
+
"derivation",
|
|
14
|
+
"nano-app",
|
|
15
|
+
"rad"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/nanobpm/nano-ide.git",
|
|
23
|
+
"directory": "packages/urban"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.6"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"urban": "src/bin.mjs"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./runtime": {
|
|
39
|
+
"types": "./dist/runtime/index.d.ts",
|
|
40
|
+
"default": "./dist/runtime/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./toolkit": {
|
|
43
|
+
"types": "./dist/toolkit/index.d.ts",
|
|
44
|
+
"default": "./dist/toolkit/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./worker": {
|
|
47
|
+
"types": "./dist/worker.d.ts",
|
|
48
|
+
"default": "./dist/worker.js"
|
|
49
|
+
},
|
|
50
|
+
"./source": "./src/index.ts"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"src",
|
|
55
|
+
"README.md"
|
|
56
|
+
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc -p tsconfig.build.json",
|
|
59
|
+
"prepack": "npm run build",
|
|
60
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
61
|
+
"test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
|
|
62
|
+
"test:deno": "deno test -A --no-check src/"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@nanobpm/nano-app-schema": "^0.5.0",
|
|
66
|
+
"@nanobpm/nano-sdk": "^1.2.3",
|
|
67
|
+
"@nanobpm/workflow": "^0.8.0",
|
|
68
|
+
"bpmn-auto-layout": "^2.0.0-alpha.2",
|
|
69
|
+
"create-urban-app": "^0.9.0",
|
|
70
|
+
"yaml": "^2.9.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^22.20.1",
|
|
74
|
+
"typescript": "^5.9.3"
|
|
75
|
+
}
|
|
76
76
|
}
|