@nanobpm/urban 0.49.1 → 0.51.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/runtime/core/form-contract.d.ts +58 -0
- package/dist/runtime/core/form-contract.js +108 -0
- package/dist/runtime/core/modules/pages.js +165 -33
- package/dist/runtime/engine/nanosdk.js +22 -39
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js +4 -0
- package/package.json +1 -1
- package/src/runtime/core/form-contract.test.ts +105 -0
- package/src/runtime/core/form-contract.ts +121 -0
- package/src/runtime/core/modules/pages.test.ts +85 -0
- package/src/runtime/core/modules/pages.ts +165 -33
- package/src/runtime/engine/nanosdk.test.ts +21 -4
- package/src/runtime/engine/nanosdk.ts +27 -36
- package/src/runtime/index.ts +15 -0
|
@@ -22,7 +22,13 @@ import type {
|
|
|
22
22
|
UserTaskSummary,
|
|
23
23
|
WorkerSubscription,
|
|
24
24
|
} from "../core/host.ts";
|
|
25
|
-
import { isBpmnError
|
|
25
|
+
import { isBpmnError } from "../core/host.ts";
|
|
26
|
+
import {
|
|
27
|
+
buildFormSchema,
|
|
28
|
+
parseFormSchema,
|
|
29
|
+
pickFormLinkage,
|
|
30
|
+
resolveFormIdentifier,
|
|
31
|
+
} from "../core/form-contract.ts";
|
|
26
32
|
import type { EngineSdkClient } from "./sdk.ts";
|
|
27
33
|
import { isRecord } from "../core/guards.ts";
|
|
28
34
|
|
|
@@ -227,12 +233,7 @@ export class SdkEngineClient implements EngineClient {
|
|
|
227
233
|
// User tasks are an eventually consistent read; ask for zero-wait consistency so
|
|
228
234
|
// the search reflects what is currently visible without blocking.
|
|
229
235
|
const body = await this.client.searchUserTasks(
|
|
230
|
-
|
|
231
|
-
// CREATED so answered/canceled tasks never surface as answerable (e.g. a
|
|
232
|
-
// re-escalated instance whose prior escalation task is already COMPLETED).
|
|
233
|
-
// Spread caller filters first and pin `state` last so a caller can never
|
|
234
|
-
// override the open-only (CREATED) constraint this method contracts to.
|
|
235
|
-
{ filter: { ...(filter ?? {}), state: "CREATED" } },
|
|
236
|
+
{ filter: { ...(filter ?? {}) } },
|
|
236
237
|
{ consistency: { waitUpToMs: 0 } },
|
|
237
238
|
);
|
|
238
239
|
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
@@ -243,19 +244,13 @@ export class SdkEngineClient implements EngineClient {
|
|
|
243
244
|
return [];
|
|
244
245
|
}
|
|
245
246
|
// The engine resolves a `formId="X"` linkage to the latest deployed form's key at
|
|
246
|
-
// task creation, so `formKey` (not a form id) is the linkage it reports on the task
|
|
247
|
-
|
|
248
|
-
it.formKey != null && it.formKey !== "" ? String(it.formKey) : undefined;
|
|
249
|
-
const externalFormReference =
|
|
250
|
-
typeof it.externalFormReference === "string" && it.externalFormReference !== ""
|
|
251
|
-
? it.externalFormReference
|
|
252
|
-
: undefined;
|
|
247
|
+
// task creation, so `formKey` (not a form id) is the linkage it reports on the task;
|
|
248
|
+
// no `resolveFormKeyByFormId` resolver is passed because the gateway already resolved it.
|
|
253
249
|
return [{
|
|
254
250
|
userTaskKey: String(userTaskKey),
|
|
255
251
|
elementId: typeof it.elementId === "string" ? it.elementId : undefined,
|
|
256
252
|
variables: isRecord(it.variables) ? it.variables : undefined,
|
|
257
|
-
...(
|
|
258
|
-
...(externalFormReference ? { externalFormReference } : {}),
|
|
253
|
+
...pickFormLinkage(it),
|
|
259
254
|
}];
|
|
260
255
|
});
|
|
261
256
|
}
|
|
@@ -268,12 +263,14 @@ export class SdkEngineClient implements EngineClient {
|
|
|
268
263
|
// form or 404s (→ null) when there is no such form.
|
|
269
264
|
//
|
|
270
265
|
// This is the single gate that resolves which identifier addresses the form: an
|
|
271
|
-
// empty/whitespace identifier is treated as *absent* (via
|
|
272
|
-
//
|
|
266
|
+
// empty/whitespace identifier is treated as *absent* (via the shared presence rule in
|
|
267
|
+
// `resolveFormIdentifier`) so a blank `formKey` (e.g. a `?formKey=` query param) falls
|
|
273
268
|
// through to a valid `formId` instead of being taken as a present-but-unresolvable key
|
|
274
|
-
// that short-circuits to null.
|
|
275
|
-
|
|
276
|
-
|
|
269
|
+
// that short-circuits to null. The REST gateway addresses a form by `value` regardless
|
|
270
|
+
// of whether it is a deploy key or an authored id, so `kind` is not consulted here.
|
|
271
|
+
const resolved = resolveFormIdentifier(input);
|
|
272
|
+
if (resolved == null) return null;
|
|
273
|
+
const key = resolved.value;
|
|
277
274
|
let body: Record<string, unknown>;
|
|
278
275
|
try {
|
|
279
276
|
body = await this.client.getFormByKey(
|
|
@@ -290,27 +287,21 @@ export class SdkEngineClient implements EngineClient {
|
|
|
290
287
|
return null;
|
|
291
288
|
}
|
|
292
289
|
if (!isRecord(body)) return null;
|
|
293
|
-
const rawSchema = body.schema;
|
|
294
290
|
// The engine serializes the form-js schema as a JSON string; parse it to the object
|
|
295
291
|
// the surface renders. Tolerate an already-parsed object (an in-memory/embedded engine).
|
|
296
|
-
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
const parsed: unknown = JSON.parse(rawSchema);
|
|
300
|
-
if (isRecord(parsed)) schema = parsed;
|
|
301
|
-
} catch {
|
|
292
|
+
const schema = parseFormSchema(body.schema);
|
|
293
|
+
if (!schema) {
|
|
294
|
+
if (typeof body.schema === "string") {
|
|
302
295
|
this.log("warn", "getForm: form schema is not valid JSON", { key, formKey: input.formKey, formId: input.formId });
|
|
303
296
|
}
|
|
304
|
-
|
|
305
|
-
schema = rawSchema;
|
|
297
|
+
return null;
|
|
306
298
|
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
299
|
+
return buildFormSchema({
|
|
309
300
|
schema,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
};
|
|
301
|
+
formKey: body.formKey,
|
|
302
|
+
formId: body.formId,
|
|
303
|
+
version: body.version,
|
|
304
|
+
});
|
|
314
305
|
}
|
|
315
306
|
|
|
316
307
|
async completeUserTask(userTaskKey: string, variables?: Record<string, unknown>): Promise<void> {
|
package/src/runtime/index.ts
CHANGED
|
@@ -52,16 +52,31 @@ export type { ValidationIssue } from "./core/validate.ts";
|
|
|
52
52
|
export type {
|
|
53
53
|
EngineClient,
|
|
54
54
|
EngineJob,
|
|
55
|
+
FormSchema,
|
|
55
56
|
HostContext,
|
|
56
57
|
HttpHandler,
|
|
57
58
|
HttpRequest,
|
|
58
59
|
HttpResponse,
|
|
59
60
|
HttpServer,
|
|
60
61
|
JobHandler,
|
|
62
|
+
ProcessInstanceSnapshot,
|
|
63
|
+
ProcessInstanceState,
|
|
61
64
|
SqliteDb,
|
|
65
|
+
UserTaskState,
|
|
66
|
+
UserTaskSummary,
|
|
62
67
|
WatchHandle,
|
|
63
68
|
WorkerSubscription,
|
|
64
69
|
} from "./core/host.ts";
|
|
70
|
+
// Shared form/user-task contract normalization (issue #252): the single source of truth
|
|
71
|
+
// both engine adapters (SDK/REST + WASM test double) call, so their form-identifier,
|
|
72
|
+
// form-schema, and user-task form-linkage handling cannot drift.
|
|
73
|
+
export {
|
|
74
|
+
buildFormSchema,
|
|
75
|
+
parseFormSchema,
|
|
76
|
+
pickFormLinkage,
|
|
77
|
+
presentFormIdentifier,
|
|
78
|
+
resolveFormIdentifier,
|
|
79
|
+
} from "./core/form-contract.ts";
|
|
65
80
|
export type { AppApi, Mounted, RuntimeContext } from "./core/context.ts";
|
|
66
81
|
// Structured logging surface (see core/logger.ts): the shape of `AppApi.log`, plus `createLogger`
|
|
67
82
|
// so consumers can build a Logger for a custom sink or a no-op test double (`createLogger(() => {})`).
|