@nanobpm/urban 0.29.0 → 0.30.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.
Files changed (44) hide show
  1. package/dist/openapi/spec.d.ts +127 -0
  2. package/dist/openapi/spec.js +444 -0
  3. package/dist/runtime/core/modules/api.d.ts +61 -0
  4. package/dist/runtime/core/modules/api.js +359 -0
  5. package/dist/runtime/core/modules/cancel.d.ts +31 -0
  6. package/dist/runtime/core/modules/cancel.js +90 -0
  7. package/dist/runtime/core/modules/datasource.d.ts +5 -2
  8. package/dist/runtime/core/modules/datasource.js +5 -4
  9. package/dist/runtime/core/modules/instance-tracking.d.ts +8 -0
  10. package/dist/runtime/core/modules/instance-tracking.js +28 -11
  11. package/dist/runtime/core/modules/pages.d.ts +8 -0
  12. package/dist/runtime/core/modules/pages.js +17 -1
  13. package/dist/runtime/core/modules/surfaces.js +9 -0
  14. package/dist/runtime/engine/nanosdk.js +41 -7
  15. package/dist/runtime/index.d.ts +4 -0
  16. package/dist/runtime/index.js +2 -0
  17. package/dist/toolkit/artifact.d.ts +5 -0
  18. package/dist/toolkit/artifact.js +7 -0
  19. package/dist/toolkit/derivers/api.d.ts +19 -0
  20. package/dist/toolkit/derivers/api.js +224 -0
  21. package/dist/toolkit/gen.js +23 -4
  22. package/dist/toolkit/index.d.ts +5 -0
  23. package/dist/toolkit/index.js +12 -1
  24. package/package.json +1 -1
  25. package/src/openapi/spec.test.ts +292 -0
  26. package/src/openapi/spec.ts +540 -0
  27. package/src/runtime/core/modules/api.test.ts +445 -0
  28. package/src/runtime/core/modules/api.ts +453 -0
  29. package/src/runtime/core/modules/cancel.test.ts +310 -0
  30. package/src/runtime/core/modules/cancel.ts +119 -0
  31. package/src/runtime/core/modules/datasource.ts +5 -4
  32. package/src/runtime/core/modules/instance-tracking.ts +33 -11
  33. package/src/runtime/core/modules/pages.test.ts +46 -2
  34. package/src/runtime/core/modules/pages.ts +23 -1
  35. package/src/runtime/core/modules/surfaces.ts +10 -0
  36. package/src/runtime/engine/nanosdk.test.ts +45 -5
  37. package/src/runtime/engine/nanosdk.ts +39 -7
  38. package/src/runtime/index.ts +12 -0
  39. package/src/toolkit/artifact.ts +8 -0
  40. package/src/toolkit/derivers/api.test.ts +219 -0
  41. package/src/toolkit/derivers/api.ts +255 -0
  42. package/src/toolkit/gen.test.ts +39 -0
  43. package/src/toolkit/gen.ts +24 -4
  44. package/src/toolkit/index.ts +41 -1
@@ -165,3 +165,42 @@ test("the sweep never touches an authored (un-stamped) .bpmn", async () => {
165
165
  await runGen({ root: "/cf", io });
166
166
  assert.ok(io.files["/cf/processes/authored.bpmn"], "authored .bpmn must survive the sweep");
167
167
  });
168
+
169
+ test("collectArtifacts trims api.spec whitespace so gen matches the runtime (no gen/runtime drift)", async () => {
170
+ const manifest = JSON.stringify({ id: "demo", data: { default: "app" }, api: { spec: " openapi.json " } });
171
+ const openapi = JSON.stringify({
172
+ openapi: "3.0.0",
173
+ paths: { "/ping": { get: { operationId: "ping", responses: { "200": {} } } } },
174
+ });
175
+ const io = memIO({ "/app/nano.app.json": manifest, "/app/openapi.json": openapi });
176
+ const res = await collectArtifacts({ root: "/app", io });
177
+ const paths = res.map((a) => a.path);
178
+ // The whitespace-padded spec path still resolved and derived the endpoint contracts.
179
+ assert.ok(paths.includes("nano-generated/api-io.d.ts"));
180
+ });
181
+
182
+ test("collectArtifacts resolves an api.spec with Windows-style separators (no gen/runtime drift)", async () => {
183
+ const manifest = JSON.stringify({ id: "demo", data: { default: "app" }, api: { spec: "specs\\openapi.json" } });
184
+ const openapi = JSON.stringify({
185
+ openapi: "3.0.0",
186
+ paths: { "/ping": { get: { operationId: "ping", responses: { "200": {} } } } },
187
+ });
188
+ const io = memIO({ "/app/nano.app.json": manifest, "/app/specs/openapi.json": openapi });
189
+ const res = await collectArtifacts({ root: "/app", io });
190
+ assert.ok(res.map((a) => a.path).includes("nano-generated/api-io.d.ts"));
191
+ });
192
+
193
+ test("collectArtifacts resolves an ABSOLUTE api.spec at its own path, not root-relative (no gen/runtime drift)", async () => {
194
+ // An absolute manifest spec path must resolve to itself — mirroring the runtime's resolveAppPath
195
+ // — not be stripped to a root-relative "/app/abs/openapi.json". Otherwise gen would read/derive a
196
+ // different file than the runtime dispatches against.
197
+ const manifest = JSON.stringify({ id: "demo", data: { default: "app" }, api: { spec: "/abs/openapi.json" } });
198
+ const openapi = JSON.stringify({
199
+ openapi: "3.0.0",
200
+ paths: { "/ping": { get: { operationId: "ping", responses: { "200": {} } } } },
201
+ });
202
+ // Only the absolute location holds the spec; a root-relative read would ENOENT.
203
+ const io = memIO({ "/app/nano.app.json": manifest, "/abs/openapi.json": openapi });
204
+ const res = await collectArtifacts({ root: "/app", io });
205
+ assert.ok(res.map((a) => a.path).includes("nano-generated/api-io.d.ts"));
206
+ });
@@ -4,12 +4,14 @@
4
4
  // pure; all IO is confined here behind a tiny FS port so the same code runs on Node and Deno.
5
5
 
6
6
  import type { DerivedArtifact } from "./artifact.ts";
7
- import { sortArtifacts } from "./artifact.ts";
7
+ import { isAbsolutePath, sortArtifacts } from "./artifact.ts";
8
8
  import { deriveMigrations, type ToolkitManifest } from "./derivers/migrations.ts";
9
9
  import { deriveDomain } from "./derivers/domain.ts";
10
10
  import { deriveWorkerBindings, type ModelSource } from "./derivers/worker-io.ts";
11
11
  import { deriveMeta } from "./derivers/meta.ts";
12
12
  import { deriveMessageBindings } from "./derivers/messages.ts";
13
+ import { deriveApi } from "./derivers/api.ts";
14
+ import { parseSpec } from "../openapi/spec.ts";
13
15
  import { deriveModels, type DerivedModels, type ModelError, MODEL_PROVENANCE } from "./models.ts";
14
16
 
15
17
  /** Minimal filesystem port. Node/Deno impls live in `fsio.ts`. */
@@ -56,9 +58,17 @@ export interface GenResult {
56
58
  }
57
59
 
58
60
  function join(root: string, rel: string): string {
59
- // Trim either separator so callers may pass Windows-style paths; GenIO
60
- // implementations accept forward slashes on all platforms.
61
- return `${root.replace(/[/\\]+$/, "")}/${rel.replace(/^[/\\]+/, "")}`;
61
+ // Normalize Windows-style separators to forward slashes (GenIO uses forward slashes on all
62
+ // platforms — matching the runtime's resolveAppPath) and trim edge separators, so callers may
63
+ // pass either style without gen/runtime drift over where a file resolves.
64
+ const norm = (s: string): string => s.replace(/\\/g, "/");
65
+ // An absolute `rel` resolves to itself — never prefixed with `root` — mirroring the runtime's
66
+ // resolveAppPath (isAbsolutePath is the shared SoT in artifact.ts). Without this an absolute
67
+ // manifest path (e.g. "/abs/openapi.json") would be stripped to root-relative and gen would
68
+ // read/derive a different file than the runtime resolves. Trailing edge separators are still
69
+ // trimmed for stable, comparable artifact keys.
70
+ if (isAbsolutePath(rel)) return norm(rel).replace(/\/+$/, "");
71
+ return `${norm(root).replace(/\/+$/, "")}/${norm(rel).replace(/^\/+/, "")}`;
62
72
  }
63
73
 
64
74
  function dirOf(p: string): string {
@@ -112,6 +122,7 @@ async function collectAll(opts: GenOptions): Promise<{ artifacts: DerivedArtifac
112
122
  const manifestPath = join(root, opts.manifestFile ?? "nano.app.json");
113
123
  const manifest: ToolkitManifest & {
114
124
  models?: { processes?: string[]; workflows?: string[] };
125
+ api?: { spec?: string };
115
126
  } = JSON.parse(await io.readText(manifestPath));
116
127
 
117
128
  const artifacts: DerivedArtifact[] = [];
@@ -142,6 +153,15 @@ async function collectAll(opts: GenOptions): Promise<{ artifacts: DerivedArtifac
142
153
  }
143
154
  }
144
155
 
156
+ // 4. OpenAPI `api` binding → typed endpoint contracts (ADR 0058). Fail-closed: a declared spec
157
+ // that is missing or malformed throws here so `urban gen`/`urban check` surfaces it. Trim the
158
+ // spec path so benign whitespace matches the runtime's readApiBinding (no gen/runtime drift).
159
+ const specRef = typeof manifest.api?.spec === "string" ? manifest.api.spec.trim() : "";
160
+ if (!opts.modelsOnly && specRef.length > 0) {
161
+ const specText = await io.readText(join(root, specRef));
162
+ artifacts.push(...deriveApi(parseSpec(specText)));
163
+ }
164
+
145
165
  return { artifacts: sortArtifacts(artifacts), derived };
146
166
  }
147
167
 
@@ -98,13 +98,53 @@ export {
98
98
  } from "./derivers/messages.ts";
99
99
  export type { MessageIo, MessageBindingDecl } from "./derivers/messages.ts";
100
100
 
101
+ export {
102
+ apiDeriver,
103
+ deriveApi,
104
+ emitApiBindings,
105
+ emitApiBindingsRuntime,
106
+ schemaToTs,
107
+ API_BINDINGS_DTS,
108
+ API_BINDINGS_TS,
109
+ } from "./derivers/api.ts";
110
+
111
+ // The OpenAPI machinery the deriver + runtime share (ADR 0058).
112
+ export {
113
+ collectOperations,
114
+ HTTP_METHODS,
115
+ isSafeOperationId,
116
+ operationsWithoutId,
117
+ operationsWithUnsafeId,
118
+ parseSpec,
119
+ refName,
120
+ resolveSchema,
121
+ toRouteMatcher,
122
+ undeclaredPathParams,
123
+ validateValue,
124
+ } from "../openapi/spec.ts";
125
+ export type {
126
+ HttpMethodLower,
127
+ OpenApiDoc,
128
+ OpenApiParameter,
129
+ OpenApiSchema,
130
+ OperationInfo,
131
+ } from "../openapi/spec.ts";
132
+
101
133
  // The registry of all derivers (for discovery / IDE migration).
102
134
  import { migrationsDeriver } from "./derivers/migrations.ts";
103
135
  import { domainDeriver } from "./derivers/domain.ts";
104
136
  import { workerIoDeriver } from "./derivers/worker-io.ts";
105
137
  import { metaDeriver } from "./derivers/meta.ts";
106
138
  import { messagesDeriver } from "./derivers/messages.ts";
107
- export const DERIVERS = [migrationsDeriver, domainDeriver, workerIoDeriver, metaDeriver, messagesDeriver] as const;
139
+ import { apiDeriver } from "./derivers/api.ts";
140
+ export const DERIVERS = [
141
+ migrationsDeriver,
142
+ domainDeriver,
143
+ workerIoDeriver,
144
+ metaDeriver,
145
+ messagesDeriver,
146
+ apiDeriver,
147
+ ] as const;
108
148
 
109
149
  // Gen orchestrator + IO
110
150
  export { collectArtifacts, runGen, joinPath, readModels, expandPattern, previewModels } from "./gen.ts";