@zauso-ai/packs-core 0.1.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/index.d.ts +209 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1574 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1574 @@
|
|
|
1
|
+
const PACKS_APPLIED = Symbol.for("capstan.graph-packs.applied");
|
|
2
|
+
const authPack = defineGraphPack({
|
|
3
|
+
key: "auth",
|
|
4
|
+
title: "Authentication Pack",
|
|
5
|
+
description: "Adds user, role, and invitation primitives for operator-facing applications.",
|
|
6
|
+
apply(context) {
|
|
7
|
+
const subjectName = readOptionString(context.selection, "subjectName") ?? "User";
|
|
8
|
+
const subjectPlural = readOptionString(context.selection, "subjectPlural") ?? pluralize(subjectName);
|
|
9
|
+
const subjectKeyStem = toPackPascalName(subjectName);
|
|
10
|
+
const subjectPluralKeyStem = toPackPascalName(subjectPlural);
|
|
11
|
+
const userResourceKey = readOptionString(context.selection, "userResourceKey") ?? "user";
|
|
12
|
+
const roleResourceKey = readOptionString(context.selection, "roleResourceKey") ?? "role";
|
|
13
|
+
const listCapabilityKey = readOptionString(context.selection, "listCapabilityKey") ?? `list${subjectPluralKeyStem}`;
|
|
14
|
+
const inviteCapabilityKey = readOptionString(context.selection, "inviteCapabilityKey") ?? `invite${subjectKeyStem}`;
|
|
15
|
+
const listViewKey = readOptionString(context.selection, "listViewKey") ?? `${userResourceKey}List`;
|
|
16
|
+
const formViewKey = readOptionString(context.selection, "formViewKey") ?? `${userResourceKey}Form`;
|
|
17
|
+
return {
|
|
18
|
+
resources: [
|
|
19
|
+
{
|
|
20
|
+
key: userResourceKey,
|
|
21
|
+
title: subjectName,
|
|
22
|
+
description: `An authenticated ${subjectName.toLowerCase()} who can operate the application.`,
|
|
23
|
+
fields: {
|
|
24
|
+
email: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
displayName: {
|
|
29
|
+
type: "string",
|
|
30
|
+
required: true
|
|
31
|
+
},
|
|
32
|
+
status: {
|
|
33
|
+
type: "string",
|
|
34
|
+
required: true,
|
|
35
|
+
constraints: {
|
|
36
|
+
enum: ["active", "invited", "disabled"]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: roleResourceKey,
|
|
43
|
+
title: "Role",
|
|
44
|
+
description: "An authorization role assignable to authenticated operators.",
|
|
45
|
+
fields: {
|
|
46
|
+
name: {
|
|
47
|
+
type: "string",
|
|
48
|
+
required: true
|
|
49
|
+
},
|
|
50
|
+
description: {
|
|
51
|
+
type: "string"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
capabilities: [
|
|
57
|
+
{
|
|
58
|
+
key: listCapabilityKey,
|
|
59
|
+
title: `List ${subjectPlural}`,
|
|
60
|
+
description: `Read the current ${subjectPlural.toLowerCase()} in the system.`,
|
|
61
|
+
mode: "read",
|
|
62
|
+
resources: [userResourceKey],
|
|
63
|
+
policy: "authenticated"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: inviteCapabilityKey,
|
|
67
|
+
title: `Invite ${subjectName}`,
|
|
68
|
+
description: `Invite a new ${subjectName.toLowerCase()} and assign an initial role.`,
|
|
69
|
+
mode: "write",
|
|
70
|
+
resources: [userResourceKey, roleResourceKey],
|
|
71
|
+
policy: "authenticated",
|
|
72
|
+
input: {
|
|
73
|
+
email: {
|
|
74
|
+
type: "string",
|
|
75
|
+
required: true
|
|
76
|
+
},
|
|
77
|
+
roleKey: {
|
|
78
|
+
type: "string",
|
|
79
|
+
required: true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
policies: [
|
|
85
|
+
{
|
|
86
|
+
key: "authenticated",
|
|
87
|
+
title: "Authenticated Access",
|
|
88
|
+
description: "Allows access only for authenticated operators.",
|
|
89
|
+
effect: "allow"
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
views: [
|
|
93
|
+
{
|
|
94
|
+
key: listViewKey,
|
|
95
|
+
title: subjectPlural,
|
|
96
|
+
kind: "list",
|
|
97
|
+
resource: userResourceKey,
|
|
98
|
+
capability: listCapabilityKey
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: formViewKey,
|
|
102
|
+
title: `Invite ${subjectName}`,
|
|
103
|
+
kind: "form",
|
|
104
|
+
resource: userResourceKey,
|
|
105
|
+
capability: inviteCapabilityKey
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const tenantPack = defineGraphPack({
|
|
112
|
+
key: "tenant",
|
|
113
|
+
title: "Organization And Tenant Pack",
|
|
114
|
+
description: "Adds multi-tenant organization and membership primitives.",
|
|
115
|
+
dependsOn: ["auth"],
|
|
116
|
+
apply(context) {
|
|
117
|
+
const entityName = readOptionString(context.selection, "entityName") ?? "Organization";
|
|
118
|
+
const entityPlural = readOptionString(context.selection, "entityPlural") ?? pluralize(entityName);
|
|
119
|
+
const entityKeyStem = toPackPascalName(entityName);
|
|
120
|
+
const entityPluralKeyStem = toPackPascalName(entityPlural);
|
|
121
|
+
const entityResourceKey = readOptionString(context.selection, "entityResourceKey") ?? toPackKey(entityName);
|
|
122
|
+
const membershipResourceKey = readOptionString(context.selection, "membershipResourceKey") ?? "membership";
|
|
123
|
+
const listEntitiesCapabilityKey = readOptionString(context.selection, "listEntitiesCapabilityKey") ??
|
|
124
|
+
`list${entityPluralKeyStem}`;
|
|
125
|
+
const provisionEntityCapabilityKey = readOptionString(context.selection, "provisionEntityCapabilityKey") ??
|
|
126
|
+
`provision${entityKeyStem}`;
|
|
127
|
+
const listMembershipsCapabilityKey = readOptionString(context.selection, "listMembershipsCapabilityKey") ?? "listMemberships";
|
|
128
|
+
const entityListViewKey = readOptionString(context.selection, "entityListViewKey") ?? `${entityResourceKey}List`;
|
|
129
|
+
const membershipListViewKey = readOptionString(context.selection, "membershipListViewKey") ?? `${membershipResourceKey}List`;
|
|
130
|
+
return {
|
|
131
|
+
resources: [
|
|
132
|
+
{
|
|
133
|
+
key: entityResourceKey,
|
|
134
|
+
title: entityName,
|
|
135
|
+
description: `A tenant container that scopes work inside the application.`,
|
|
136
|
+
fields: {
|
|
137
|
+
name: {
|
|
138
|
+
type: "string",
|
|
139
|
+
required: true
|
|
140
|
+
},
|
|
141
|
+
slug: {
|
|
142
|
+
type: "string",
|
|
143
|
+
required: true
|
|
144
|
+
},
|
|
145
|
+
status: {
|
|
146
|
+
type: "string",
|
|
147
|
+
required: true,
|
|
148
|
+
constraints: {
|
|
149
|
+
enum: ["active", "suspended"]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
key: membershipResourceKey,
|
|
156
|
+
title: `${entityName} Membership`,
|
|
157
|
+
description: `A link between one user and one ${entityName.toLowerCase()}.`,
|
|
158
|
+
fields: {
|
|
159
|
+
userId: {
|
|
160
|
+
type: "string",
|
|
161
|
+
required: true
|
|
162
|
+
},
|
|
163
|
+
[`${entityResourceKey}Id`]: {
|
|
164
|
+
type: "string",
|
|
165
|
+
required: true
|
|
166
|
+
},
|
|
167
|
+
roleKey: {
|
|
168
|
+
type: "string",
|
|
169
|
+
required: true
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
capabilities: [
|
|
175
|
+
{
|
|
176
|
+
key: listEntitiesCapabilityKey,
|
|
177
|
+
title: `List ${entityPlural}`,
|
|
178
|
+
description: `Read the ${entityPlural.toLowerCase()} that scope work inside the application.`,
|
|
179
|
+
mode: "read",
|
|
180
|
+
resources: [entityResourceKey],
|
|
181
|
+
policy: "tenantScoped"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
key: provisionEntityCapabilityKey,
|
|
185
|
+
title: `Provision ${entityName}`,
|
|
186
|
+
description: `Create a new ${entityName.toLowerCase()} in a controlled way.`,
|
|
187
|
+
mode: "write",
|
|
188
|
+
resources: [entityResourceKey],
|
|
189
|
+
policy: "tenantScoped",
|
|
190
|
+
input: {
|
|
191
|
+
name: {
|
|
192
|
+
type: "string",
|
|
193
|
+
required: true
|
|
194
|
+
},
|
|
195
|
+
slug: {
|
|
196
|
+
type: "string",
|
|
197
|
+
required: true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: listMembershipsCapabilityKey,
|
|
203
|
+
title: "List Memberships",
|
|
204
|
+
description: `Read membership assignments for ${entityPlural.toLowerCase()}.`,
|
|
205
|
+
mode: "read",
|
|
206
|
+
resources: [membershipResourceKey, entityResourceKey, "user"],
|
|
207
|
+
policy: "tenantScoped"
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
policies: [
|
|
211
|
+
{
|
|
212
|
+
key: "tenantScoped",
|
|
213
|
+
title: "Tenant Scoped Access",
|
|
214
|
+
description: "Allows access only when a request is correctly scoped to one tenant.",
|
|
215
|
+
effect: "allow"
|
|
216
|
+
}
|
|
217
|
+
],
|
|
218
|
+
views: [
|
|
219
|
+
{
|
|
220
|
+
key: entityListViewKey,
|
|
221
|
+
title: entityPlural,
|
|
222
|
+
kind: "list",
|
|
223
|
+
resource: entityResourceKey,
|
|
224
|
+
capability: listEntitiesCapabilityKey
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
key: membershipListViewKey,
|
|
228
|
+
title: "Memberships",
|
|
229
|
+
kind: "list",
|
|
230
|
+
resource: membershipResourceKey,
|
|
231
|
+
capability: listMembershipsCapabilityKey
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
const workflowPack = defineGraphPack({
|
|
238
|
+
key: "workflow",
|
|
239
|
+
title: "Workflow Pack",
|
|
240
|
+
description: "Adds durable workflow requests, approval semantics, and generated artifacts.",
|
|
241
|
+
dependsOn: ["auth"],
|
|
242
|
+
apply(context) {
|
|
243
|
+
const entityName = readOptionString(context.selection, "entityName") ?? "Work Request";
|
|
244
|
+
const entityPlural = readOptionString(context.selection, "entityPlural") ?? pluralize(entityName);
|
|
245
|
+
const entityKeyStem = toPackPascalName(entityName);
|
|
246
|
+
const entityPluralKeyStem = toPackPascalName(entityPlural);
|
|
247
|
+
const resourceKey = readOptionString(context.selection, "resourceKey") ?? toPackKey(entityName);
|
|
248
|
+
const listCapabilityKey = readOptionString(context.selection, "listCapabilityKey") ?? `list${entityPluralKeyStem}`;
|
|
249
|
+
const submitCapabilityKey = readOptionString(context.selection, "submitCapabilityKey") ?? `submit${entityKeyStem}`;
|
|
250
|
+
const processCapabilityKey = readOptionString(context.selection, "processCapabilityKey") ?? `process${entityKeyStem}`;
|
|
251
|
+
const taskKey = readOptionString(context.selection, "taskKey") ?? `${processCapabilityKey}Task`;
|
|
252
|
+
const artifactKey = readOptionString(context.selection, "artifactKey") ?? `${resourceKey}Report`;
|
|
253
|
+
const approvalPolicyKey = readOptionString(context.selection, "approvalPolicyKey") ?? "workflowApprovalRequired";
|
|
254
|
+
const listViewKey = readOptionString(context.selection, "listViewKey") ?? `${resourceKey}List`;
|
|
255
|
+
const formViewKey = readOptionString(context.selection, "formViewKey") ?? `${resourceKey}Form`;
|
|
256
|
+
const detailViewKey = readOptionString(context.selection, "detailViewKey") ?? `${resourceKey}Detail`;
|
|
257
|
+
const accessPolicyKey = getAccessPolicyKey(context);
|
|
258
|
+
const durableRuntime = createDurableRuntimeFragments({
|
|
259
|
+
taskKey,
|
|
260
|
+
taskTitle: `Process ${entityName} Task`,
|
|
261
|
+
taskDescription: `Durably processes one ${entityName.toLowerCase()} through review and completion.`,
|
|
262
|
+
artifactKey,
|
|
263
|
+
artifactTitle: `${entityName} Report`,
|
|
264
|
+
artifactDescription: `A generated report artifact produced when one ${entityName.toLowerCase()} finishes processing.`,
|
|
265
|
+
artifactKind: "report",
|
|
266
|
+
approvalPolicyKey,
|
|
267
|
+
approvalTitle: `${entityName} Approval Required`,
|
|
268
|
+
approvalDescription: `Requires explicit approval before ${entityName.toLowerCase()} processing may continue.`
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
resources: [
|
|
272
|
+
{
|
|
273
|
+
key: resourceKey,
|
|
274
|
+
title: entityName,
|
|
275
|
+
description: `A durable workflow request that can be reviewed and completed over time.`,
|
|
276
|
+
fields: {
|
|
277
|
+
title: {
|
|
278
|
+
type: "string",
|
|
279
|
+
required: true
|
|
280
|
+
},
|
|
281
|
+
summary: {
|
|
282
|
+
type: "string"
|
|
283
|
+
},
|
|
284
|
+
status: {
|
|
285
|
+
type: "string",
|
|
286
|
+
required: true,
|
|
287
|
+
constraints: {
|
|
288
|
+
enum: ["draft", "submitted", "in_review", "completed", "blocked"]
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
requestedById: {
|
|
292
|
+
type: "string",
|
|
293
|
+
required: true
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
],
|
|
298
|
+
capabilities: [
|
|
299
|
+
{
|
|
300
|
+
key: listCapabilityKey,
|
|
301
|
+
title: `List ${entityPlural}`,
|
|
302
|
+
description: `Read the ${entityPlural.toLowerCase()} currently tracked by the workflow system.`,
|
|
303
|
+
mode: "read",
|
|
304
|
+
resources: [resourceKey],
|
|
305
|
+
policy: accessPolicyKey
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: submitCapabilityKey,
|
|
309
|
+
title: `Submit ${entityName}`,
|
|
310
|
+
description: `Create and submit a new ${entityName.toLowerCase()} for review.`,
|
|
311
|
+
mode: "write",
|
|
312
|
+
resources: [resourceKey],
|
|
313
|
+
policy: accessPolicyKey,
|
|
314
|
+
input: {
|
|
315
|
+
title: {
|
|
316
|
+
type: "string",
|
|
317
|
+
required: true
|
|
318
|
+
},
|
|
319
|
+
summary: {
|
|
320
|
+
type: "string"
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
key: processCapabilityKey,
|
|
326
|
+
title: `Process ${entityName}`,
|
|
327
|
+
description: `Advance one ${entityName.toLowerCase()} through a durable review workflow.`,
|
|
328
|
+
mode: "external",
|
|
329
|
+
resources: [resourceKey],
|
|
330
|
+
policy: approvalPolicyKey,
|
|
331
|
+
task: taskKey,
|
|
332
|
+
input: {
|
|
333
|
+
[`${resourceKey}Id`]: {
|
|
334
|
+
type: "string",
|
|
335
|
+
required: true
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
tasks: durableRuntime.tasks,
|
|
341
|
+
policies: durableRuntime.policies,
|
|
342
|
+
artifacts: durableRuntime.artifacts,
|
|
343
|
+
views: [
|
|
344
|
+
{
|
|
345
|
+
key: listViewKey,
|
|
346
|
+
title: entityPlural,
|
|
347
|
+
kind: "list",
|
|
348
|
+
resource: resourceKey,
|
|
349
|
+
capability: listCapabilityKey
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: formViewKey,
|
|
353
|
+
title: `Submit ${entityName}`,
|
|
354
|
+
kind: "form",
|
|
355
|
+
resource: resourceKey,
|
|
356
|
+
capability: submitCapabilityKey
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
key: detailViewKey,
|
|
360
|
+
title: `${entityName} Detail`,
|
|
361
|
+
kind: "detail",
|
|
362
|
+
resource: resourceKey,
|
|
363
|
+
capability: processCapabilityKey
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
const connectorPack = defineGraphPack({
|
|
370
|
+
key: "connector",
|
|
371
|
+
title: "Connector Pack",
|
|
372
|
+
description: "Adds external system connectors, durable sync tasks, and sync artifacts.",
|
|
373
|
+
dependsOn: ["tenant"],
|
|
374
|
+
apply(context) {
|
|
375
|
+
const entityName = readOptionString(context.selection, "entityName") ?? "Connector";
|
|
376
|
+
const entityPlural = readOptionString(context.selection, "entityPlural") ?? pluralize(entityName);
|
|
377
|
+
const entityKeyStem = toPackPascalName(entityName);
|
|
378
|
+
const entityPluralKeyStem = toPackPascalName(entityPlural);
|
|
379
|
+
const resourceKey = readOptionString(context.selection, "resourceKey") ?? toPackKey(entityName);
|
|
380
|
+
const listCapabilityKey = readOptionString(context.selection, "listCapabilityKey") ?? `list${entityPluralKeyStem}`;
|
|
381
|
+
const configureCapabilityKey = readOptionString(context.selection, "configureCapabilityKey") ??
|
|
382
|
+
`configure${entityKeyStem}`;
|
|
383
|
+
const syncCapabilityKey = readOptionString(context.selection, "syncCapabilityKey") ?? `sync${entityKeyStem}`;
|
|
384
|
+
const taskKey = readOptionString(context.selection, "taskKey") ?? `${syncCapabilityKey}Task`;
|
|
385
|
+
const artifactKey = readOptionString(context.selection, "artifactKey") ?? `${resourceKey}SyncReport`;
|
|
386
|
+
const approvalPolicyKey = readOptionString(context.selection, "approvalPolicyKey") ?? "connectorSyncApprovalRequired";
|
|
387
|
+
const listViewKey = readOptionString(context.selection, "listViewKey") ?? `${resourceKey}List`;
|
|
388
|
+
const formViewKey = readOptionString(context.selection, "formViewKey") ?? `${resourceKey}Form`;
|
|
389
|
+
const detailViewKey = readOptionString(context.selection, "detailViewKey") ?? `${resourceKey}Detail`;
|
|
390
|
+
const accessPolicyKey = getAccessPolicyKey(context);
|
|
391
|
+
const durableRuntime = createDurableRuntimeFragments({
|
|
392
|
+
taskKey,
|
|
393
|
+
taskTitle: `Sync ${entityName} Task`,
|
|
394
|
+
taskDescription: `Durably synchronizes one ${entityName.toLowerCase()} and records the result.`,
|
|
395
|
+
artifactKey,
|
|
396
|
+
artifactTitle: `${entityName} Sync Report`,
|
|
397
|
+
artifactDescription: `A generated sync report artifact produced after one ${entityName.toLowerCase()} sync completes.`,
|
|
398
|
+
artifactKind: "report",
|
|
399
|
+
approvalPolicyKey,
|
|
400
|
+
approvalTitle: `${entityName} Sync Approval Required`,
|
|
401
|
+
approvalDescription: `Requires approval before ${entityName.toLowerCase()} sync may continue.`
|
|
402
|
+
});
|
|
403
|
+
return {
|
|
404
|
+
resources: [
|
|
405
|
+
{
|
|
406
|
+
key: resourceKey,
|
|
407
|
+
title: entityName,
|
|
408
|
+
description: `A configured external ${entityName.toLowerCase()} that can sync data into the application.`,
|
|
409
|
+
fields: {
|
|
410
|
+
name: {
|
|
411
|
+
type: "string",
|
|
412
|
+
required: true
|
|
413
|
+
},
|
|
414
|
+
provider: {
|
|
415
|
+
type: "string",
|
|
416
|
+
required: true
|
|
417
|
+
},
|
|
418
|
+
status: {
|
|
419
|
+
type: "string",
|
|
420
|
+
required: true,
|
|
421
|
+
constraints: {
|
|
422
|
+
enum: ["connected", "degraded", "disconnected"]
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
lastSyncedAt: {
|
|
426
|
+
type: "datetime"
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
],
|
|
431
|
+
capabilities: [
|
|
432
|
+
{
|
|
433
|
+
key: listCapabilityKey,
|
|
434
|
+
title: `List ${entityPlural}`,
|
|
435
|
+
description: `Read the configured ${entityPlural.toLowerCase()} available to this application.`,
|
|
436
|
+
mode: "read",
|
|
437
|
+
resources: [resourceKey],
|
|
438
|
+
policy: accessPolicyKey
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
key: configureCapabilityKey,
|
|
442
|
+
title: `Configure ${entityName}`,
|
|
443
|
+
description: `Create or update a configured ${entityName.toLowerCase()} for one tenant.`,
|
|
444
|
+
mode: "write",
|
|
445
|
+
resources: [resourceKey],
|
|
446
|
+
policy: accessPolicyKey,
|
|
447
|
+
input: {
|
|
448
|
+
name: {
|
|
449
|
+
type: "string",
|
|
450
|
+
required: true
|
|
451
|
+
},
|
|
452
|
+
provider: {
|
|
453
|
+
type: "string",
|
|
454
|
+
required: true
|
|
455
|
+
},
|
|
456
|
+
secretReference: {
|
|
457
|
+
type: "string"
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
key: syncCapabilityKey,
|
|
463
|
+
title: `Sync ${entityName}`,
|
|
464
|
+
description: `Trigger a durable sync for one configured ${entityName.toLowerCase()}.`,
|
|
465
|
+
mode: "external",
|
|
466
|
+
resources: [resourceKey],
|
|
467
|
+
policy: approvalPolicyKey,
|
|
468
|
+
task: taskKey,
|
|
469
|
+
input: {
|
|
470
|
+
[`${resourceKey}Id`]: {
|
|
471
|
+
type: "string",
|
|
472
|
+
required: true
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
],
|
|
477
|
+
tasks: durableRuntime.tasks,
|
|
478
|
+
policies: durableRuntime.policies,
|
|
479
|
+
artifacts: durableRuntime.artifacts,
|
|
480
|
+
views: [
|
|
481
|
+
{
|
|
482
|
+
key: listViewKey,
|
|
483
|
+
title: entityPlural,
|
|
484
|
+
kind: "list",
|
|
485
|
+
resource: resourceKey,
|
|
486
|
+
capability: listCapabilityKey
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
key: formViewKey,
|
|
490
|
+
title: `Configure ${entityName}`,
|
|
491
|
+
kind: "form",
|
|
492
|
+
resource: resourceKey,
|
|
493
|
+
capability: configureCapabilityKey
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
key: detailViewKey,
|
|
497
|
+
title: `${entityName} Detail`,
|
|
498
|
+
kind: "detail",
|
|
499
|
+
resource: resourceKey,
|
|
500
|
+
capability: syncCapabilityKey
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
const billingPack = createLinkedEntityPack({
|
|
507
|
+
key: "billing",
|
|
508
|
+
title: "Billing Pack",
|
|
509
|
+
description: "Adds subscription and invoice primitives, durable collection tasks, and billing receipts.",
|
|
510
|
+
dependsOn: ["tenant"],
|
|
511
|
+
optionKeys: {
|
|
512
|
+
primaryName: "subscriptionName",
|
|
513
|
+
primaryPlural: "subscriptionPlural",
|
|
514
|
+
primaryResourceKey: "subscriptionResourceKey",
|
|
515
|
+
secondaryName: "invoiceName",
|
|
516
|
+
secondaryPlural: "invoicePlural",
|
|
517
|
+
secondaryResourceKey: "invoiceResourceKey",
|
|
518
|
+
primaryListCapabilityKey: "listSubscriptionsCapabilityKey",
|
|
519
|
+
primaryWriteCapabilityKey: "provisionSubscriptionCapabilityKey",
|
|
520
|
+
secondaryListCapabilityKey: "listInvoicesCapabilityKey",
|
|
521
|
+
secondaryExecuteCapabilityKey: "collectInvoicePaymentCapabilityKey",
|
|
522
|
+
taskKey: "taskKey",
|
|
523
|
+
artifactKey: "artifactKey",
|
|
524
|
+
approvalPolicyKey: "approvalPolicyKey",
|
|
525
|
+
primaryListViewKey: "subscriptionListViewKey",
|
|
526
|
+
primaryFormViewKey: "subscriptionFormViewKey",
|
|
527
|
+
secondaryListViewKey: "invoiceListViewKey",
|
|
528
|
+
secondaryDetailViewKey: "invoiceDetailViewKey"
|
|
529
|
+
},
|
|
530
|
+
primary: {
|
|
531
|
+
name: "Subscription",
|
|
532
|
+
resourceKey: "subscription",
|
|
533
|
+
description: (names) => `A billable ${names.primaryName.toLowerCase()} that governs service access for one tenant.`,
|
|
534
|
+
fields: {
|
|
535
|
+
planName: {
|
|
536
|
+
type: "string",
|
|
537
|
+
required: true
|
|
538
|
+
},
|
|
539
|
+
status: {
|
|
540
|
+
type: "string",
|
|
541
|
+
required: true,
|
|
542
|
+
constraints: {
|
|
543
|
+
enum: ["trial", "active", "past_due", "canceled"]
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
billingEmail: {
|
|
547
|
+
type: "string",
|
|
548
|
+
required: true
|
|
549
|
+
},
|
|
550
|
+
renewsAt: {
|
|
551
|
+
type: "datetime"
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
secondary: {
|
|
556
|
+
name: "Invoice",
|
|
557
|
+
resourceKey: "invoice",
|
|
558
|
+
description: (names) => `A billable ${names.secondaryName.toLowerCase()} tied to one ${names.primaryName.toLowerCase()}.`,
|
|
559
|
+
fields: (names) => ({
|
|
560
|
+
[`${names.primaryResourceKey}Id`]: {
|
|
561
|
+
type: "string",
|
|
562
|
+
required: true
|
|
563
|
+
},
|
|
564
|
+
amountCents: {
|
|
565
|
+
type: "integer",
|
|
566
|
+
required: true,
|
|
567
|
+
constraints: {
|
|
568
|
+
minimum: 0
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
currency: {
|
|
572
|
+
type: "string",
|
|
573
|
+
required: true
|
|
574
|
+
},
|
|
575
|
+
status: {
|
|
576
|
+
type: "string",
|
|
577
|
+
required: true,
|
|
578
|
+
constraints: {
|
|
579
|
+
enum: ["draft", "open", "paid", "failed", "void"]
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
dueAt: {
|
|
583
|
+
type: "date"
|
|
584
|
+
},
|
|
585
|
+
collectedAt: {
|
|
586
|
+
type: "datetime"
|
|
587
|
+
}
|
|
588
|
+
})
|
|
589
|
+
},
|
|
590
|
+
primaryList: {
|
|
591
|
+
description: (names) => `Read the ${names.primaryPlural.toLowerCase()} currently tracked by the billing system.`
|
|
592
|
+
},
|
|
593
|
+
primaryWrite: {
|
|
594
|
+
capabilityKey: (names) => `provision${names.primaryKeyStem}`,
|
|
595
|
+
title: (names) => `Provision ${names.primaryName}`,
|
|
596
|
+
description: (names) => `Create a new ${names.primaryName.toLowerCase()} and attach it to one tenant.`,
|
|
597
|
+
input: {
|
|
598
|
+
planName: {
|
|
599
|
+
type: "string",
|
|
600
|
+
required: true
|
|
601
|
+
},
|
|
602
|
+
billingEmail: {
|
|
603
|
+
type: "string",
|
|
604
|
+
required: true
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
viewTitle: (names) => `Provision ${names.primaryName}`
|
|
608
|
+
},
|
|
609
|
+
secondaryList: {
|
|
610
|
+
description: (names) => `Read the ${names.secondaryPlural.toLowerCase()} generated by the billing system.`
|
|
611
|
+
},
|
|
612
|
+
secondaryExecute: {
|
|
613
|
+
capabilityKey: (names) => `collect${names.secondaryKeyStem}Payment`,
|
|
614
|
+
title: (names) => `Collect ${names.secondaryName} Payment`,
|
|
615
|
+
description: (names) => `Run a durable payment collection flow for one ${names.secondaryName.toLowerCase()}.`,
|
|
616
|
+
taskTitle: (names) => `Collect ${names.secondaryName} Payment Task`,
|
|
617
|
+
taskDescription: (names) => `Durably collects payment for one ${names.secondaryName.toLowerCase()} and records the receipt.`,
|
|
618
|
+
artifactKey: (names) => `${names.secondaryResourceKey}CollectionReceipt`,
|
|
619
|
+
artifactTitle: (names) => `${names.secondaryName} Collection Receipt`,
|
|
620
|
+
artifactDescription: (names) => `A structured receipt artifact produced after one ${names.secondaryName.toLowerCase()} payment collection completes.`,
|
|
621
|
+
artifactKind: "record",
|
|
622
|
+
approvalPolicyKey: "billingCollectionApprovalRequired",
|
|
623
|
+
approvalTitle: (names) => `${names.secondaryName} Collection Approval Required`,
|
|
624
|
+
approvalDescription: (names) => `Requires approval before ${names.secondaryName.toLowerCase()} collection may continue.`,
|
|
625
|
+
viewTitle: (names) => `${names.secondaryName} Detail`
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
const commercePack = createLinkedEntityPack({
|
|
629
|
+
key: "commerce",
|
|
630
|
+
title: "Commerce Pack",
|
|
631
|
+
description: "Adds catalog and order primitives, durable fulfillment tasks, and fulfillment receipts.",
|
|
632
|
+
dependsOn: ["tenant"],
|
|
633
|
+
optionKeys: {
|
|
634
|
+
primaryName: "catalogItemName",
|
|
635
|
+
primaryPlural: "catalogItemPlural",
|
|
636
|
+
primaryResourceKey: "catalogItemResourceKey",
|
|
637
|
+
secondaryName: "orderName",
|
|
638
|
+
secondaryPlural: "orderPlural",
|
|
639
|
+
secondaryResourceKey: "orderResourceKey",
|
|
640
|
+
primaryListCapabilityKey: "listCatalogCapabilityKey",
|
|
641
|
+
primaryWriteCapabilityKey: "upsertCatalogCapabilityKey",
|
|
642
|
+
secondaryListCapabilityKey: "listOrdersCapabilityKey",
|
|
643
|
+
secondaryExecuteCapabilityKey: "fulfillOrderCapabilityKey",
|
|
644
|
+
taskKey: "taskKey",
|
|
645
|
+
artifactKey: "artifactKey",
|
|
646
|
+
approvalPolicyKey: "approvalPolicyKey",
|
|
647
|
+
primaryListViewKey: "catalogListViewKey",
|
|
648
|
+
primaryFormViewKey: "catalogFormViewKey",
|
|
649
|
+
secondaryListViewKey: "orderListViewKey",
|
|
650
|
+
secondaryDetailViewKey: "orderDetailViewKey"
|
|
651
|
+
},
|
|
652
|
+
primary: {
|
|
653
|
+
name: "Catalog Item",
|
|
654
|
+
resourceKey: "catalogItem",
|
|
655
|
+
description: (names) => `A sellable ${names.primaryName.toLowerCase()} in the application catalog.`,
|
|
656
|
+
fields: {
|
|
657
|
+
title: {
|
|
658
|
+
type: "string",
|
|
659
|
+
required: true
|
|
660
|
+
},
|
|
661
|
+
sku: {
|
|
662
|
+
type: "string",
|
|
663
|
+
required: true
|
|
664
|
+
},
|
|
665
|
+
priceCents: {
|
|
666
|
+
type: "integer",
|
|
667
|
+
required: true,
|
|
668
|
+
constraints: {
|
|
669
|
+
minimum: 0
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
status: {
|
|
673
|
+
type: "string",
|
|
674
|
+
required: true,
|
|
675
|
+
constraints: {
|
|
676
|
+
enum: ["draft", "active", "archived"]
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
secondary: {
|
|
682
|
+
name: "Order",
|
|
683
|
+
resourceKey: "order",
|
|
684
|
+
description: (names) => `A customer-facing ${names.secondaryName.toLowerCase()} that references one or more catalog items.`,
|
|
685
|
+
fields: (names) => ({
|
|
686
|
+
[`${names.primaryResourceKey}Id`]: {
|
|
687
|
+
type: "string",
|
|
688
|
+
required: true
|
|
689
|
+
},
|
|
690
|
+
quantity: {
|
|
691
|
+
type: "integer",
|
|
692
|
+
required: true,
|
|
693
|
+
constraints: {
|
|
694
|
+
minimum: 1
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
totalCents: {
|
|
698
|
+
type: "integer",
|
|
699
|
+
required: true,
|
|
700
|
+
constraints: {
|
|
701
|
+
minimum: 0
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
status: {
|
|
705
|
+
type: "string",
|
|
706
|
+
required: true,
|
|
707
|
+
constraints: {
|
|
708
|
+
enum: ["draft", "confirmed", "fulfilling", "fulfilled", "canceled"]
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
})
|
|
712
|
+
},
|
|
713
|
+
primaryList: {
|
|
714
|
+
description: (names) => `Read the ${names.primaryPlural.toLowerCase()} available to be ordered.`
|
|
715
|
+
},
|
|
716
|
+
primaryWrite: {
|
|
717
|
+
capabilityKey: (names) => `upsert${names.primaryKeyStem}`,
|
|
718
|
+
title: (names) => `Upsert ${names.primaryName}`,
|
|
719
|
+
description: (names) => `Create or update one ${names.primaryName.toLowerCase()} in the application catalog.`,
|
|
720
|
+
input: {
|
|
721
|
+
title: {
|
|
722
|
+
type: "string",
|
|
723
|
+
required: true
|
|
724
|
+
},
|
|
725
|
+
sku: {
|
|
726
|
+
type: "string",
|
|
727
|
+
required: true
|
|
728
|
+
},
|
|
729
|
+
priceCents: {
|
|
730
|
+
type: "integer",
|
|
731
|
+
required: true
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
viewTitle: (names) => `Upsert ${names.primaryName}`
|
|
735
|
+
},
|
|
736
|
+
secondaryList: {
|
|
737
|
+
description: (names) => `Read the ${names.secondaryPlural.toLowerCase()} currently managed by the commerce system.`
|
|
738
|
+
},
|
|
739
|
+
secondaryExecute: {
|
|
740
|
+
capabilityKey: (names) => `fulfill${names.secondaryKeyStem}`,
|
|
741
|
+
title: (names) => `Fulfill ${names.secondaryName}`,
|
|
742
|
+
description: (names) => `Run a durable fulfillment flow for one ${names.secondaryName.toLowerCase()}.`,
|
|
743
|
+
taskTitle: (names) => `Fulfill ${names.secondaryName} Task`,
|
|
744
|
+
taskDescription: (names) => `Durably fulfills one ${names.secondaryName.toLowerCase()} and records the fulfillment receipt.`,
|
|
745
|
+
artifactKey: (names) => `${names.secondaryResourceKey}FulfillmentReceipt`,
|
|
746
|
+
artifactTitle: (names) => `${names.secondaryName} Fulfillment Receipt`,
|
|
747
|
+
artifactDescription: (names) => `A structured receipt artifact produced after one ${names.secondaryName.toLowerCase()} fulfillment completes.`,
|
|
748
|
+
artifactKind: "record",
|
|
749
|
+
approvalPolicyKey: "commerceFulfillmentApprovalRequired",
|
|
750
|
+
approvalTitle: (names) => `${names.secondaryName} Fulfillment Approval Required`,
|
|
751
|
+
approvalDescription: (names) => `Requires approval before ${names.secondaryName.toLowerCase()} fulfillment may continue.`,
|
|
752
|
+
viewTitle: (names) => `${names.secondaryName} Detail`
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
const revenueOpsPack = createDurableEntityPack({
|
|
756
|
+
key: "revenueOps",
|
|
757
|
+
title: "Revenue Operations Pack",
|
|
758
|
+
description: "Bundles commerce, billing, and connectors, then adds a durable reconciliation surface on top.",
|
|
759
|
+
dependsOn: ["commerce", "billing", "connector"],
|
|
760
|
+
entity: {
|
|
761
|
+
name: "Revenue Ops Exception",
|
|
762
|
+
resourceKey: "revenueOpsException",
|
|
763
|
+
description: "A tracked issue raised when commerce, billing, or connector state drifts out of sync.",
|
|
764
|
+
fields: {
|
|
765
|
+
title: {
|
|
766
|
+
type: "string",
|
|
767
|
+
required: true
|
|
768
|
+
},
|
|
769
|
+
severity: {
|
|
770
|
+
type: "string",
|
|
771
|
+
required: true,
|
|
772
|
+
constraints: {
|
|
773
|
+
enum: ["low", "medium", "high", "critical"]
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
status: {
|
|
777
|
+
type: "string",
|
|
778
|
+
required: true,
|
|
779
|
+
constraints: {
|
|
780
|
+
enum: ["open", "investigating", "resolved"]
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
ownerId: {
|
|
784
|
+
type: "string"
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
list: {
|
|
789
|
+
description: "Read the revenue operations issues currently tracked across commerce and billing flows."
|
|
790
|
+
},
|
|
791
|
+
write: {
|
|
792
|
+
capabilityKey: "captureRevenueOpsException",
|
|
793
|
+
title: (names) => `Capture ${names.entityName}`,
|
|
794
|
+
description: "Create or update a revenue operations issue when a cross-system mismatch is discovered.",
|
|
795
|
+
input: {
|
|
796
|
+
title: {
|
|
797
|
+
type: "string",
|
|
798
|
+
required: true
|
|
799
|
+
},
|
|
800
|
+
severity: {
|
|
801
|
+
type: "string",
|
|
802
|
+
required: true
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
viewTitle: (names) => `Capture ${names.entityName}`
|
|
806
|
+
},
|
|
807
|
+
execute: {
|
|
808
|
+
capabilityKey: "reconcileRevenueOps",
|
|
809
|
+
title: "Reconcile Revenue Ops",
|
|
810
|
+
description: "Run a durable reconciliation flow across commerce, billing, and connector state.",
|
|
811
|
+
input: {
|
|
812
|
+
tenantId: {
|
|
813
|
+
type: "string",
|
|
814
|
+
required: true
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
taskTitle: "Reconcile Revenue Ops Task",
|
|
818
|
+
taskDescription: "Durably reconciles commerce, billing, and connector state into one operator digest.",
|
|
819
|
+
artifactKey: "revenueOpsDigest",
|
|
820
|
+
artifactTitle: "Revenue Ops Digest",
|
|
821
|
+
artifactDescription: "A generated digest artifact produced after one revenue operations reconciliation completes.",
|
|
822
|
+
artifactKind: "report",
|
|
823
|
+
approvalPolicyKey: "revenueOpsApprovalRequired",
|
|
824
|
+
approvalTitle: "Revenue Ops Approval Required",
|
|
825
|
+
approvalDescription: "Requires approval before revenue operations reconciliation may continue.",
|
|
826
|
+
viewTitle: (names) => `${names.entityName} Detail`
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
const builtinGraphPacks = [authPack, tenantPack, workflowPack, connectorPack, billingPack, commercePack, revenueOpsPack];
|
|
830
|
+
export function defineGraphPack(definition) {
|
|
831
|
+
return definition;
|
|
832
|
+
}
|
|
833
|
+
export function createDurableEntityPack(definition) {
|
|
834
|
+
return defineGraphPack({
|
|
835
|
+
key: definition.key,
|
|
836
|
+
title: definition.title,
|
|
837
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
838
|
+
...(definition.dependsOn ? { dependsOn: definition.dependsOn } : {}),
|
|
839
|
+
apply(context) {
|
|
840
|
+
const names = resolveDurableEntityPackNames(context, definition);
|
|
841
|
+
const resourceFields = resolveRequiredTemplateRecord(definition.entity.fields, names);
|
|
842
|
+
const resourceTitle = renderTemplateValue(definition.entity.title, names) ?? names.entityName;
|
|
843
|
+
const resourceDescription = renderTemplateValue(definition.entity.description, names) ??
|
|
844
|
+
`A durable ${names.entityName.toLowerCase()} managed by this pack.`;
|
|
845
|
+
const listDescription = renderTemplateValue(definition.list?.description, names);
|
|
846
|
+
const listResources = resolveTemplateRecord(definition.list?.resources, names) ?? [
|
|
847
|
+
names.resourceKey
|
|
848
|
+
];
|
|
849
|
+
const writeDescription = renderTemplateValue(definition.write.description, names);
|
|
850
|
+
const writeResources = resolveTemplateRecord(definition.write.resources, names) ?? [
|
|
851
|
+
names.resourceKey
|
|
852
|
+
];
|
|
853
|
+
const writeInput = resolveRequiredTemplateRecord(definition.write.input, names);
|
|
854
|
+
const executeDescription = renderTemplateValue(definition.execute.description, names);
|
|
855
|
+
const executeResources = resolveTemplateRecord(definition.execute.resources, names) ?? [
|
|
856
|
+
names.resourceKey
|
|
857
|
+
];
|
|
858
|
+
const executeInput = resolveTemplateRecord(definition.execute.input, names) ??
|
|
859
|
+
defaultExecuteInput(names);
|
|
860
|
+
const durableRuntime = createDurableRuntimeFragments({
|
|
861
|
+
taskKey: names.taskKey,
|
|
862
|
+
taskTitle: renderTemplateValue(definition.execute.taskTitle, names) ??
|
|
863
|
+
`${names.executeCapabilityKey} Task`,
|
|
864
|
+
taskDescription: renderTemplateValue(definition.execute.taskDescription, names) ??
|
|
865
|
+
`Durably executes ${names.executeCapabilityKey} and records one artifact.`,
|
|
866
|
+
artifactKey: names.artifactKey,
|
|
867
|
+
artifactTitle: renderTemplateValue(definition.execute.artifactTitle, names) ?? names.artifactKey,
|
|
868
|
+
artifactDescription: renderTemplateValue(definition.execute.artifactDescription, names) ??
|
|
869
|
+
`A generated artifact produced after ${names.executeCapabilityKey} completes.`,
|
|
870
|
+
artifactKind: definition.execute.artifactKind,
|
|
871
|
+
approvalPolicyKey: names.approvalPolicyKey,
|
|
872
|
+
approvalTitle: renderTemplateValue(definition.execute.approvalTitle, names) ??
|
|
873
|
+
`${names.entityName} Approval Required`,
|
|
874
|
+
approvalDescription: renderTemplateValue(definition.execute.approvalDescription, names) ??
|
|
875
|
+
`Requires approval before ${names.executeCapabilityKey} may continue.`
|
|
876
|
+
});
|
|
877
|
+
return {
|
|
878
|
+
resources: [
|
|
879
|
+
{
|
|
880
|
+
key: names.resourceKey,
|
|
881
|
+
title: resourceTitle,
|
|
882
|
+
description: resourceDescription,
|
|
883
|
+
fields: resourceFields
|
|
884
|
+
}
|
|
885
|
+
],
|
|
886
|
+
capabilities: [
|
|
887
|
+
{
|
|
888
|
+
key: names.listCapabilityKey,
|
|
889
|
+
title: renderTemplateValue(definition.list?.title, names) ??
|
|
890
|
+
`List ${names.entityPlural}`,
|
|
891
|
+
...(listDescription ? { description: listDescription } : {}),
|
|
892
|
+
mode: "read",
|
|
893
|
+
resources: listResources,
|
|
894
|
+
policy: names.accessPolicyKey
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
key: names.writeCapabilityKey,
|
|
898
|
+
title: renderTemplateValue(definition.write.title, names) ??
|
|
899
|
+
`Upsert ${names.entityName}`,
|
|
900
|
+
...(writeDescription ? { description: writeDescription } : {}),
|
|
901
|
+
mode: "write",
|
|
902
|
+
resources: writeResources,
|
|
903
|
+
policy: names.accessPolicyKey,
|
|
904
|
+
input: writeInput
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
key: names.executeCapabilityKey,
|
|
908
|
+
title: renderTemplateValue(definition.execute.title, names) ??
|
|
909
|
+
`Execute ${names.entityName}`,
|
|
910
|
+
...(executeDescription ? { description: executeDescription } : {}),
|
|
911
|
+
mode: "external",
|
|
912
|
+
resources: executeResources,
|
|
913
|
+
policy: names.approvalPolicyKey,
|
|
914
|
+
task: names.taskKey,
|
|
915
|
+
input: executeInput
|
|
916
|
+
}
|
|
917
|
+
],
|
|
918
|
+
tasks: durableRuntime.tasks,
|
|
919
|
+
policies: durableRuntime.policies,
|
|
920
|
+
artifacts: durableRuntime.artifacts,
|
|
921
|
+
views: [
|
|
922
|
+
{
|
|
923
|
+
key: names.listViewKey,
|
|
924
|
+
title: renderTemplateValue(definition.list?.viewTitle, names) ?? names.entityPlural,
|
|
925
|
+
kind: "list",
|
|
926
|
+
resource: names.resourceKey,
|
|
927
|
+
capability: names.listCapabilityKey
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
key: names.formViewKey,
|
|
931
|
+
title: renderTemplateValue(definition.write.viewTitle, names) ??
|
|
932
|
+
`Upsert ${names.entityName}`,
|
|
933
|
+
kind: "form",
|
|
934
|
+
resource: names.resourceKey,
|
|
935
|
+
capability: names.writeCapabilityKey
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
key: names.detailViewKey,
|
|
939
|
+
title: renderTemplateValue(definition.execute.viewTitle, names) ??
|
|
940
|
+
`${names.entityName} Detail`,
|
|
941
|
+
kind: "detail",
|
|
942
|
+
resource: names.resourceKey,
|
|
943
|
+
capability: names.executeCapabilityKey
|
|
944
|
+
}
|
|
945
|
+
]
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
export function createLinkedEntityPack(definition) {
|
|
951
|
+
return defineGraphPack({
|
|
952
|
+
key: definition.key,
|
|
953
|
+
title: definition.title,
|
|
954
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
955
|
+
...(definition.dependsOn ? { dependsOn: definition.dependsOn } : {}),
|
|
956
|
+
apply(context) {
|
|
957
|
+
const names = resolveLinkedEntityPackNames(context, definition);
|
|
958
|
+
const primaryTitle = renderTemplateValue(definition.primary.title, names) ?? names.primaryName;
|
|
959
|
+
const primaryDescription = renderTemplateValue(definition.primary.description, names) ??
|
|
960
|
+
`A durable ${names.primaryName.toLowerCase()} managed by this pack.`;
|
|
961
|
+
const secondaryTitle = renderTemplateValue(definition.secondary.title, names) ?? names.secondaryName;
|
|
962
|
+
const secondaryDescription = renderTemplateValue(definition.secondary.description, names) ??
|
|
963
|
+
`A durable ${names.secondaryName.toLowerCase()} managed by this pack.`;
|
|
964
|
+
const primaryFields = resolveRequiredTemplateRecord(definition.primary.fields, names);
|
|
965
|
+
const secondaryFields = resolveRequiredTemplateRecord(definition.secondary.fields, names);
|
|
966
|
+
const primaryListDescription = renderTemplateValue(definition.primaryList?.description, names);
|
|
967
|
+
const primaryWriteDescription = renderTemplateValue(definition.primaryWrite.description, names);
|
|
968
|
+
const secondaryListDescription = renderTemplateValue(definition.secondaryList?.description, names);
|
|
969
|
+
const secondaryExecuteDescription = renderTemplateValue(definition.secondaryExecute.description, names);
|
|
970
|
+
const primaryListResources = resolveTemplateRecord(definition.primaryList?.resources, names) ?? [names.primaryResourceKey];
|
|
971
|
+
const primaryWriteResources = resolveTemplateRecord(definition.primaryWrite.resources, names) ?? [names.primaryResourceKey];
|
|
972
|
+
const secondaryListResources = resolveTemplateRecord(definition.secondaryList?.resources, names) ?? [names.secondaryResourceKey, names.primaryResourceKey];
|
|
973
|
+
const secondaryExecuteResources = resolveTemplateRecord(definition.secondaryExecute.resources, names) ?? [names.secondaryResourceKey, names.primaryResourceKey];
|
|
974
|
+
const primaryWriteInput = resolveRequiredTemplateRecord(definition.primaryWrite.input, names);
|
|
975
|
+
const secondaryExecuteInput = resolveTemplateRecord(definition.secondaryExecute.input, names) ??
|
|
976
|
+
defaultLinkedExecuteInput(names);
|
|
977
|
+
const durableRuntime = createDurableRuntimeFragments({
|
|
978
|
+
taskKey: names.taskKey,
|
|
979
|
+
taskTitle: renderTemplateValue(definition.secondaryExecute.taskTitle, names) ??
|
|
980
|
+
`${names.secondaryExecuteCapabilityKey} Task`,
|
|
981
|
+
taskDescription: renderTemplateValue(definition.secondaryExecute.taskDescription, names) ??
|
|
982
|
+
`Durably executes ${names.secondaryExecuteCapabilityKey} and records one artifact.`,
|
|
983
|
+
artifactKey: names.artifactKey,
|
|
984
|
+
artifactTitle: renderTemplateValue(definition.secondaryExecute.artifactTitle, names) ??
|
|
985
|
+
names.artifactKey,
|
|
986
|
+
artifactDescription: renderTemplateValue(definition.secondaryExecute.artifactDescription, names) ??
|
|
987
|
+
`A generated artifact produced after ${names.secondaryExecuteCapabilityKey} completes.`,
|
|
988
|
+
artifactKind: definition.secondaryExecute.artifactKind,
|
|
989
|
+
approvalPolicyKey: names.approvalPolicyKey,
|
|
990
|
+
approvalTitle: renderTemplateValue(definition.secondaryExecute.approvalTitle, names) ??
|
|
991
|
+
`${names.secondaryName} Approval Required`,
|
|
992
|
+
approvalDescription: renderTemplateValue(definition.secondaryExecute.approvalDescription, names) ??
|
|
993
|
+
`Requires approval before ${names.secondaryExecuteCapabilityKey} may continue.`
|
|
994
|
+
});
|
|
995
|
+
return {
|
|
996
|
+
resources: [
|
|
997
|
+
{
|
|
998
|
+
key: names.primaryResourceKey,
|
|
999
|
+
title: primaryTitle,
|
|
1000
|
+
description: primaryDescription,
|
|
1001
|
+
fields: primaryFields
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
key: names.secondaryResourceKey,
|
|
1005
|
+
title: secondaryTitle,
|
|
1006
|
+
description: secondaryDescription,
|
|
1007
|
+
fields: secondaryFields
|
|
1008
|
+
}
|
|
1009
|
+
],
|
|
1010
|
+
capabilities: [
|
|
1011
|
+
{
|
|
1012
|
+
key: names.primaryListCapabilityKey,
|
|
1013
|
+
title: renderTemplateValue(definition.primaryList?.title, names) ??
|
|
1014
|
+
`List ${names.primaryPlural}`,
|
|
1015
|
+
...(primaryListDescription ? { description: primaryListDescription } : {}),
|
|
1016
|
+
mode: "read",
|
|
1017
|
+
resources: primaryListResources,
|
|
1018
|
+
policy: names.accessPolicyKey
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
key: names.primaryWriteCapabilityKey,
|
|
1022
|
+
title: renderTemplateValue(definition.primaryWrite.title, names) ??
|
|
1023
|
+
`Upsert ${names.primaryName}`,
|
|
1024
|
+
...(primaryWriteDescription ? { description: primaryWriteDescription } : {}),
|
|
1025
|
+
mode: "write",
|
|
1026
|
+
resources: primaryWriteResources,
|
|
1027
|
+
policy: names.accessPolicyKey,
|
|
1028
|
+
input: primaryWriteInput
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
key: names.secondaryListCapabilityKey,
|
|
1032
|
+
title: renderTemplateValue(definition.secondaryList?.title, names) ??
|
|
1033
|
+
`List ${names.secondaryPlural}`,
|
|
1034
|
+
...(secondaryListDescription ? { description: secondaryListDescription } : {}),
|
|
1035
|
+
mode: "read",
|
|
1036
|
+
resources: secondaryListResources,
|
|
1037
|
+
policy: names.accessPolicyKey
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
key: names.secondaryExecuteCapabilityKey,
|
|
1041
|
+
title: renderTemplateValue(definition.secondaryExecute.title, names) ??
|
|
1042
|
+
`Execute ${names.secondaryName}`,
|
|
1043
|
+
...(secondaryExecuteDescription ? { description: secondaryExecuteDescription } : {}),
|
|
1044
|
+
mode: "external",
|
|
1045
|
+
resources: secondaryExecuteResources,
|
|
1046
|
+
policy: names.approvalPolicyKey,
|
|
1047
|
+
task: names.taskKey,
|
|
1048
|
+
input: secondaryExecuteInput
|
|
1049
|
+
}
|
|
1050
|
+
],
|
|
1051
|
+
tasks: durableRuntime.tasks,
|
|
1052
|
+
policies: durableRuntime.policies,
|
|
1053
|
+
artifacts: durableRuntime.artifacts,
|
|
1054
|
+
views: [
|
|
1055
|
+
{
|
|
1056
|
+
key: names.primaryListViewKey,
|
|
1057
|
+
title: renderTemplateValue(definition.primaryList?.viewTitle, names) ??
|
|
1058
|
+
names.primaryPlural,
|
|
1059
|
+
kind: "list",
|
|
1060
|
+
resource: names.primaryResourceKey,
|
|
1061
|
+
capability: names.primaryListCapabilityKey
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
key: names.primaryFormViewKey,
|
|
1065
|
+
title: renderTemplateValue(definition.primaryWrite.viewTitle, names) ??
|
|
1066
|
+
`Upsert ${names.primaryName}`,
|
|
1067
|
+
kind: "form",
|
|
1068
|
+
resource: names.primaryResourceKey,
|
|
1069
|
+
capability: names.primaryWriteCapabilityKey
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
key: names.secondaryListViewKey,
|
|
1073
|
+
title: renderTemplateValue(definition.secondaryList?.viewTitle, names) ??
|
|
1074
|
+
names.secondaryPlural,
|
|
1075
|
+
kind: "list",
|
|
1076
|
+
resource: names.secondaryResourceKey,
|
|
1077
|
+
capability: names.secondaryListCapabilityKey
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
key: names.secondaryDetailViewKey,
|
|
1081
|
+
title: renderTemplateValue(definition.secondaryExecute.viewTitle, names) ??
|
|
1082
|
+
`${names.secondaryName} Detail`,
|
|
1083
|
+
kind: "detail",
|
|
1084
|
+
resource: names.secondaryResourceKey,
|
|
1085
|
+
capability: names.secondaryExecuteCapabilityKey
|
|
1086
|
+
}
|
|
1087
|
+
]
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
export function listBuiltinGraphPacks() {
|
|
1093
|
+
return [...builtinGraphPacks];
|
|
1094
|
+
}
|
|
1095
|
+
export function applyBuiltinAppGraphPacks(graph) {
|
|
1096
|
+
return applyAppGraphPacks(graph, builtinGraphPacks);
|
|
1097
|
+
}
|
|
1098
|
+
export function applyAppGraphPacks(graph, packs) {
|
|
1099
|
+
if (graph[PACKS_APPLIED]) {
|
|
1100
|
+
return graph;
|
|
1101
|
+
}
|
|
1102
|
+
const registry = new Map(packs.map((pack) => [pack.key, pack]));
|
|
1103
|
+
const resolvedSelections = resolvePackSelections(graph.packs ?? [], registry);
|
|
1104
|
+
const composed = cloneGraph(graph);
|
|
1105
|
+
composed.packs = resolvedSelections;
|
|
1106
|
+
const appliedPackKeys = [];
|
|
1107
|
+
for (const selection of resolvedSelections) {
|
|
1108
|
+
const pack = registry.get(selection.key);
|
|
1109
|
+
if (!pack) {
|
|
1110
|
+
throw new Error(`Unknown pack "${selection.key}".`);
|
|
1111
|
+
}
|
|
1112
|
+
const contribution = pack.apply({
|
|
1113
|
+
graph: cloneGraph(composed),
|
|
1114
|
+
selection,
|
|
1115
|
+
appliedPackKeys: [...appliedPackKeys]
|
|
1116
|
+
});
|
|
1117
|
+
composed.resources = mergeCollection(composed.resources, contribution.resources ?? [], "resource", selection.key);
|
|
1118
|
+
composed.capabilities = mergeCollection(composed.capabilities, contribution.capabilities ?? [], "capability", selection.key);
|
|
1119
|
+
composed.tasks = mergeCollection(composed.tasks ?? [], contribution.tasks ?? [], "task", selection.key);
|
|
1120
|
+
composed.policies = mergeCollection(composed.policies ?? [], contribution.policies ?? [], "policy", selection.key);
|
|
1121
|
+
composed.artifacts = mergeCollection(composed.artifacts ?? [], contribution.artifacts ?? [], "artifact", selection.key);
|
|
1122
|
+
composed.views = mergeCollection(composed.views ?? [], contribution.views ?? [], "view", selection.key);
|
|
1123
|
+
appliedPackKeys.push(selection.key);
|
|
1124
|
+
}
|
|
1125
|
+
return markPacksApplied(composed);
|
|
1126
|
+
}
|
|
1127
|
+
export function resolvePackSelections(selections, registry) {
|
|
1128
|
+
const explicitSelections = new Map();
|
|
1129
|
+
for (const selection of selections) {
|
|
1130
|
+
const key = selection.key.trim();
|
|
1131
|
+
if (!key) {
|
|
1132
|
+
throw new Error("Pack selections must not be empty.");
|
|
1133
|
+
}
|
|
1134
|
+
if (explicitSelections.has(key)) {
|
|
1135
|
+
throw new Error(`Duplicate pack selection "${key}".`);
|
|
1136
|
+
}
|
|
1137
|
+
explicitSelections.set(key, {
|
|
1138
|
+
key,
|
|
1139
|
+
...(selection.options ? { options: cloneOptions(selection.options) } : {})
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
const orderedSelections = [];
|
|
1143
|
+
const visiting = new Set();
|
|
1144
|
+
const visited = new Set();
|
|
1145
|
+
const visit = (key) => {
|
|
1146
|
+
if (visited.has(key)) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (visiting.has(key)) {
|
|
1150
|
+
throw new Error(`Pack dependency cycle detected at "${key}".`);
|
|
1151
|
+
}
|
|
1152
|
+
const pack = registry.get(key);
|
|
1153
|
+
if (!pack) {
|
|
1154
|
+
throw new Error(`Unknown pack "${key}".`);
|
|
1155
|
+
}
|
|
1156
|
+
visiting.add(key);
|
|
1157
|
+
for (const dependency of pack.dependsOn ?? []) {
|
|
1158
|
+
visit(dependency);
|
|
1159
|
+
}
|
|
1160
|
+
visiting.delete(key);
|
|
1161
|
+
visited.add(key);
|
|
1162
|
+
const explicitSelection = explicitSelections.get(key);
|
|
1163
|
+
orderedSelections.push(explicitSelection
|
|
1164
|
+
? explicitSelection
|
|
1165
|
+
: {
|
|
1166
|
+
key
|
|
1167
|
+
});
|
|
1168
|
+
};
|
|
1169
|
+
for (const selection of explicitSelections.values()) {
|
|
1170
|
+
visit(selection.key);
|
|
1171
|
+
}
|
|
1172
|
+
return orderedSelections;
|
|
1173
|
+
}
|
|
1174
|
+
function mergeCollection(current, additions, label, packKey) {
|
|
1175
|
+
const keys = new Set(current.map((entry) => entry.key.trim()));
|
|
1176
|
+
const merged = [...current];
|
|
1177
|
+
for (const addition of additions) {
|
|
1178
|
+
const key = addition.key.trim();
|
|
1179
|
+
if (!key) {
|
|
1180
|
+
throw new Error(`Pack "${packKey}" tried to contribute an empty ${label} key.`);
|
|
1181
|
+
}
|
|
1182
|
+
if (keys.has(key)) {
|
|
1183
|
+
throw new Error(`Pack "${packKey}" cannot contribute ${label} "${key}" because the key already exists.`);
|
|
1184
|
+
}
|
|
1185
|
+
keys.add(key);
|
|
1186
|
+
merged.push(addition);
|
|
1187
|
+
}
|
|
1188
|
+
return merged;
|
|
1189
|
+
}
|
|
1190
|
+
function getAccessPolicyKey(context) {
|
|
1191
|
+
return context.appliedPackKeys.includes("tenant") ? "tenantScoped" : "authenticated";
|
|
1192
|
+
}
|
|
1193
|
+
function resolveDurableEntityPackNames(context, definition) {
|
|
1194
|
+
const entityName = readOptionString(context.selection, "entityName") ?? definition.entity.name;
|
|
1195
|
+
const entityPlural = readOptionString(context.selection, "entityPlural") ??
|
|
1196
|
+
definition.entity.plural ??
|
|
1197
|
+
pluralize(entityName);
|
|
1198
|
+
const entityKeyStem = toPackPascalName(entityName);
|
|
1199
|
+
const entityPluralKeyStem = toPackPascalName(entityPlural);
|
|
1200
|
+
const resourceKey = readOptionString(context.selection, "resourceKey") ??
|
|
1201
|
+
definition.entity.resourceKey ??
|
|
1202
|
+
toPackKey(entityName);
|
|
1203
|
+
const executeCapabilityKey = readOptionString(context.selection, "executeCapabilityKey") ??
|
|
1204
|
+
definition.execute.capabilityKey ??
|
|
1205
|
+
`execute${entityKeyStem}`;
|
|
1206
|
+
return {
|
|
1207
|
+
entityName,
|
|
1208
|
+
entityPlural,
|
|
1209
|
+
resourceKey,
|
|
1210
|
+
entityKeyStem,
|
|
1211
|
+
entityPluralKeyStem,
|
|
1212
|
+
accessPolicyKey: getAccessPolicyKey(context),
|
|
1213
|
+
listCapabilityKey: readOptionString(context.selection, "listCapabilityKey") ??
|
|
1214
|
+
definition.list?.capabilityKey ??
|
|
1215
|
+
`list${entityPluralKeyStem}`,
|
|
1216
|
+
writeCapabilityKey: readOptionString(context.selection, "writeCapabilityKey") ??
|
|
1217
|
+
definition.write.capabilityKey ??
|
|
1218
|
+
`upsert${entityKeyStem}`,
|
|
1219
|
+
executeCapabilityKey,
|
|
1220
|
+
taskKey: readOptionString(context.selection, "taskKey") ??
|
|
1221
|
+
definition.execute.taskKey ??
|
|
1222
|
+
`${executeCapabilityKey}Task`,
|
|
1223
|
+
artifactKey: readOptionString(context.selection, "artifactKey") ??
|
|
1224
|
+
definition.execute.artifactKey ??
|
|
1225
|
+
`${resourceKey}Artifact`,
|
|
1226
|
+
approvalPolicyKey: readOptionString(context.selection, "approvalPolicyKey") ??
|
|
1227
|
+
definition.execute.approvalPolicyKey ??
|
|
1228
|
+
`${resourceKey}ApprovalRequired`,
|
|
1229
|
+
listViewKey: readOptionString(context.selection, "listViewKey") ??
|
|
1230
|
+
definition.list?.viewKey ??
|
|
1231
|
+
`${resourceKey}List`,
|
|
1232
|
+
formViewKey: readOptionString(context.selection, "formViewKey") ??
|
|
1233
|
+
definition.write.viewKey ??
|
|
1234
|
+
`${resourceKey}Form`,
|
|
1235
|
+
detailViewKey: readOptionString(context.selection, "detailViewKey") ??
|
|
1236
|
+
definition.execute.viewKey ??
|
|
1237
|
+
`${resourceKey}Detail`
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function resolveLinkedEntityPackNames(context, definition) {
|
|
1241
|
+
const optionKeys = definition.optionKeys ?? {};
|
|
1242
|
+
const primaryName = readOptionString(context.selection, optionKeys.primaryName ?? "primaryName") ??
|
|
1243
|
+
definition.primary.name;
|
|
1244
|
+
const primaryPlural = readOptionString(context.selection, optionKeys.primaryPlural ?? "primaryPlural") ??
|
|
1245
|
+
definition.primary.plural ??
|
|
1246
|
+
pluralize(primaryName);
|
|
1247
|
+
const primaryKeyStem = toPackPascalName(primaryName);
|
|
1248
|
+
const primaryPluralKeyStem = toPackPascalName(primaryPlural);
|
|
1249
|
+
const primaryResourceKey = readOptionString(context.selection, optionKeys.primaryResourceKey ?? "primaryResourceKey") ??
|
|
1250
|
+
definition.primary.resourceKey ??
|
|
1251
|
+
toPackKey(primaryName);
|
|
1252
|
+
const secondaryName = readOptionString(context.selection, optionKeys.secondaryName ?? "secondaryName") ??
|
|
1253
|
+
definition.secondary.name;
|
|
1254
|
+
const secondaryPlural = readOptionString(context.selection, optionKeys.secondaryPlural ?? "secondaryPlural") ??
|
|
1255
|
+
definition.secondary.plural ??
|
|
1256
|
+
pluralize(secondaryName);
|
|
1257
|
+
const secondaryKeyStem = toPackPascalName(secondaryName);
|
|
1258
|
+
const secondaryPluralKeyStem = toPackPascalName(secondaryPlural);
|
|
1259
|
+
const secondaryResourceKey = readOptionString(context.selection, optionKeys.secondaryResourceKey ?? "secondaryResourceKey") ??
|
|
1260
|
+
definition.secondary.resourceKey ??
|
|
1261
|
+
toPackKey(secondaryName);
|
|
1262
|
+
const accessPolicyKey = getAccessPolicyKey(context);
|
|
1263
|
+
const primaryListCapabilityKey = readOptionString(context.selection, optionKeys.primaryListCapabilityKey ?? "primaryListCapabilityKey") ??
|
|
1264
|
+
renderTemplateValue(definition.primaryList?.capabilityKey, {
|
|
1265
|
+
primaryName,
|
|
1266
|
+
primaryPlural,
|
|
1267
|
+
primaryResourceKey,
|
|
1268
|
+
primaryKeyStem,
|
|
1269
|
+
primaryPluralKeyStem,
|
|
1270
|
+
secondaryName,
|
|
1271
|
+
secondaryPlural,
|
|
1272
|
+
secondaryResourceKey,
|
|
1273
|
+
secondaryKeyStem,
|
|
1274
|
+
secondaryPluralKeyStem,
|
|
1275
|
+
accessPolicyKey
|
|
1276
|
+
}) ??
|
|
1277
|
+
`list${primaryPluralKeyStem}`;
|
|
1278
|
+
const primaryWriteCapabilityKey = readOptionString(context.selection, optionKeys.primaryWriteCapabilityKey ?? "primaryWriteCapabilityKey") ??
|
|
1279
|
+
renderTemplateValue(definition.primaryWrite.capabilityKey, {
|
|
1280
|
+
primaryName,
|
|
1281
|
+
primaryPlural,
|
|
1282
|
+
primaryResourceKey,
|
|
1283
|
+
primaryKeyStem,
|
|
1284
|
+
primaryPluralKeyStem,
|
|
1285
|
+
secondaryName,
|
|
1286
|
+
secondaryPlural,
|
|
1287
|
+
secondaryResourceKey,
|
|
1288
|
+
secondaryKeyStem,
|
|
1289
|
+
secondaryPluralKeyStem,
|
|
1290
|
+
accessPolicyKey
|
|
1291
|
+
}) ??
|
|
1292
|
+
`upsert${primaryKeyStem}`;
|
|
1293
|
+
const secondaryListCapabilityKey = readOptionString(context.selection, optionKeys.secondaryListCapabilityKey ?? "secondaryListCapabilityKey") ??
|
|
1294
|
+
renderTemplateValue(definition.secondaryList?.capabilityKey, {
|
|
1295
|
+
primaryName,
|
|
1296
|
+
primaryPlural,
|
|
1297
|
+
primaryResourceKey,
|
|
1298
|
+
primaryKeyStem,
|
|
1299
|
+
primaryPluralKeyStem,
|
|
1300
|
+
secondaryName,
|
|
1301
|
+
secondaryPlural,
|
|
1302
|
+
secondaryResourceKey,
|
|
1303
|
+
secondaryKeyStem,
|
|
1304
|
+
secondaryPluralKeyStem,
|
|
1305
|
+
accessPolicyKey
|
|
1306
|
+
}) ??
|
|
1307
|
+
`list${secondaryPluralKeyStem}`;
|
|
1308
|
+
const secondaryExecuteCapabilityKey = readOptionString(context.selection, optionKeys.secondaryExecuteCapabilityKey ?? "secondaryExecuteCapabilityKey") ??
|
|
1309
|
+
renderTemplateValue(definition.secondaryExecute.capabilityKey, {
|
|
1310
|
+
primaryName,
|
|
1311
|
+
primaryPlural,
|
|
1312
|
+
primaryResourceKey,
|
|
1313
|
+
primaryKeyStem,
|
|
1314
|
+
primaryPluralKeyStem,
|
|
1315
|
+
secondaryName,
|
|
1316
|
+
secondaryPlural,
|
|
1317
|
+
secondaryResourceKey,
|
|
1318
|
+
secondaryKeyStem,
|
|
1319
|
+
secondaryPluralKeyStem,
|
|
1320
|
+
accessPolicyKey,
|
|
1321
|
+
primaryListCapabilityKey,
|
|
1322
|
+
primaryWriteCapabilityKey,
|
|
1323
|
+
secondaryListCapabilityKey
|
|
1324
|
+
}) ??
|
|
1325
|
+
`execute${secondaryKeyStem}`;
|
|
1326
|
+
const taskKey = readOptionString(context.selection, optionKeys.taskKey ?? "taskKey") ??
|
|
1327
|
+
renderTemplateValue(definition.secondaryExecute.taskKey, {
|
|
1328
|
+
primaryName,
|
|
1329
|
+
primaryPlural,
|
|
1330
|
+
primaryResourceKey,
|
|
1331
|
+
primaryKeyStem,
|
|
1332
|
+
primaryPluralKeyStem,
|
|
1333
|
+
secondaryName,
|
|
1334
|
+
secondaryPlural,
|
|
1335
|
+
secondaryResourceKey,
|
|
1336
|
+
secondaryKeyStem,
|
|
1337
|
+
secondaryPluralKeyStem,
|
|
1338
|
+
accessPolicyKey,
|
|
1339
|
+
primaryListCapabilityKey,
|
|
1340
|
+
primaryWriteCapabilityKey,
|
|
1341
|
+
secondaryListCapabilityKey,
|
|
1342
|
+
secondaryExecuteCapabilityKey
|
|
1343
|
+
}) ??
|
|
1344
|
+
`${secondaryExecuteCapabilityKey}Task`;
|
|
1345
|
+
const artifactKey = readOptionString(context.selection, optionKeys.artifactKey ?? "artifactKey") ??
|
|
1346
|
+
renderTemplateValue(definition.secondaryExecute.artifactKey, {
|
|
1347
|
+
primaryName,
|
|
1348
|
+
primaryPlural,
|
|
1349
|
+
primaryResourceKey,
|
|
1350
|
+
primaryKeyStem,
|
|
1351
|
+
primaryPluralKeyStem,
|
|
1352
|
+
secondaryName,
|
|
1353
|
+
secondaryPlural,
|
|
1354
|
+
secondaryResourceKey,
|
|
1355
|
+
secondaryKeyStem,
|
|
1356
|
+
secondaryPluralKeyStem,
|
|
1357
|
+
accessPolicyKey,
|
|
1358
|
+
primaryListCapabilityKey,
|
|
1359
|
+
primaryWriteCapabilityKey,
|
|
1360
|
+
secondaryListCapabilityKey,
|
|
1361
|
+
secondaryExecuteCapabilityKey,
|
|
1362
|
+
taskKey
|
|
1363
|
+
}) ??
|
|
1364
|
+
`${secondaryResourceKey}Artifact`;
|
|
1365
|
+
const approvalPolicyKey = readOptionString(context.selection, optionKeys.approvalPolicyKey ?? "approvalPolicyKey") ??
|
|
1366
|
+
renderTemplateValue(definition.secondaryExecute.approvalPolicyKey, {
|
|
1367
|
+
primaryName,
|
|
1368
|
+
primaryPlural,
|
|
1369
|
+
primaryResourceKey,
|
|
1370
|
+
primaryKeyStem,
|
|
1371
|
+
primaryPluralKeyStem,
|
|
1372
|
+
secondaryName,
|
|
1373
|
+
secondaryPlural,
|
|
1374
|
+
secondaryResourceKey,
|
|
1375
|
+
secondaryKeyStem,
|
|
1376
|
+
secondaryPluralKeyStem,
|
|
1377
|
+
accessPolicyKey,
|
|
1378
|
+
primaryListCapabilityKey,
|
|
1379
|
+
primaryWriteCapabilityKey,
|
|
1380
|
+
secondaryListCapabilityKey,
|
|
1381
|
+
secondaryExecuteCapabilityKey,
|
|
1382
|
+
taskKey,
|
|
1383
|
+
artifactKey
|
|
1384
|
+
}) ??
|
|
1385
|
+
`${secondaryResourceKey}ApprovalRequired`;
|
|
1386
|
+
const primaryListViewKey = readOptionString(context.selection, optionKeys.primaryListViewKey ?? "primaryListViewKey") ??
|
|
1387
|
+
renderTemplateValue(definition.primaryList?.viewKey, {
|
|
1388
|
+
primaryName,
|
|
1389
|
+
primaryPlural,
|
|
1390
|
+
primaryResourceKey
|
|
1391
|
+
}) ??
|
|
1392
|
+
`${primaryResourceKey}List`;
|
|
1393
|
+
const primaryFormViewKey = readOptionString(context.selection, optionKeys.primaryFormViewKey ?? "primaryFormViewKey") ??
|
|
1394
|
+
renderTemplateValue(definition.primaryWrite.viewKey, {
|
|
1395
|
+
primaryName,
|
|
1396
|
+
primaryPlural,
|
|
1397
|
+
primaryResourceKey
|
|
1398
|
+
}) ??
|
|
1399
|
+
`${primaryResourceKey}Form`;
|
|
1400
|
+
const secondaryListViewKey = readOptionString(context.selection, optionKeys.secondaryListViewKey ?? "secondaryListViewKey") ??
|
|
1401
|
+
renderTemplateValue(definition.secondaryList?.viewKey, {
|
|
1402
|
+
secondaryName,
|
|
1403
|
+
secondaryPlural,
|
|
1404
|
+
secondaryResourceKey
|
|
1405
|
+
}) ??
|
|
1406
|
+
`${secondaryResourceKey}List`;
|
|
1407
|
+
const secondaryDetailViewKey = readOptionString(context.selection, optionKeys.secondaryDetailViewKey ?? "secondaryDetailViewKey") ??
|
|
1408
|
+
renderTemplateValue(definition.secondaryExecute.viewKey, {
|
|
1409
|
+
secondaryName,
|
|
1410
|
+
secondaryPlural,
|
|
1411
|
+
secondaryResourceKey
|
|
1412
|
+
}) ??
|
|
1413
|
+
`${secondaryResourceKey}Detail`;
|
|
1414
|
+
return {
|
|
1415
|
+
primaryName,
|
|
1416
|
+
primaryPlural,
|
|
1417
|
+
primaryResourceKey,
|
|
1418
|
+
primaryKeyStem,
|
|
1419
|
+
primaryPluralKeyStem,
|
|
1420
|
+
secondaryName,
|
|
1421
|
+
secondaryPlural,
|
|
1422
|
+
secondaryResourceKey,
|
|
1423
|
+
secondaryKeyStem,
|
|
1424
|
+
secondaryPluralKeyStem,
|
|
1425
|
+
accessPolicyKey,
|
|
1426
|
+
primaryListCapabilityKey,
|
|
1427
|
+
primaryWriteCapabilityKey,
|
|
1428
|
+
secondaryListCapabilityKey,
|
|
1429
|
+
secondaryExecuteCapabilityKey,
|
|
1430
|
+
taskKey,
|
|
1431
|
+
artifactKey,
|
|
1432
|
+
approvalPolicyKey,
|
|
1433
|
+
primaryListViewKey,
|
|
1434
|
+
primaryFormViewKey,
|
|
1435
|
+
secondaryListViewKey,
|
|
1436
|
+
secondaryDetailViewKey
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function resolveTemplateRecord(value, names) {
|
|
1440
|
+
if (!value) {
|
|
1441
|
+
return undefined;
|
|
1442
|
+
}
|
|
1443
|
+
return typeof value === "function"
|
|
1444
|
+
? value(names)
|
|
1445
|
+
: value;
|
|
1446
|
+
}
|
|
1447
|
+
function resolveRequiredTemplateRecord(value, names) {
|
|
1448
|
+
return typeof value === "function"
|
|
1449
|
+
? value(names)
|
|
1450
|
+
: value;
|
|
1451
|
+
}
|
|
1452
|
+
function renderTemplateValue(value, names) {
|
|
1453
|
+
if (!value) {
|
|
1454
|
+
return undefined;
|
|
1455
|
+
}
|
|
1456
|
+
return typeof value === "function" ? value(names) : value;
|
|
1457
|
+
}
|
|
1458
|
+
function defaultExecuteInput(names) {
|
|
1459
|
+
return {
|
|
1460
|
+
[`${names.resourceKey}Id`]: {
|
|
1461
|
+
type: "string",
|
|
1462
|
+
required: true
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
function defaultLinkedExecuteInput(names) {
|
|
1467
|
+
return {
|
|
1468
|
+
[`${names.secondaryResourceKey}Id`]: {
|
|
1469
|
+
type: "string",
|
|
1470
|
+
required: true
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
function createDurableRuntimeFragments(config) {
|
|
1475
|
+
return {
|
|
1476
|
+
tasks: [
|
|
1477
|
+
{
|
|
1478
|
+
key: config.taskKey,
|
|
1479
|
+
title: config.taskTitle,
|
|
1480
|
+
description: config.taskDescription,
|
|
1481
|
+
kind: "durable",
|
|
1482
|
+
artifacts: [config.artifactKey]
|
|
1483
|
+
}
|
|
1484
|
+
],
|
|
1485
|
+
policies: [
|
|
1486
|
+
{
|
|
1487
|
+
key: config.approvalPolicyKey,
|
|
1488
|
+
title: config.approvalTitle,
|
|
1489
|
+
description: config.approvalDescription,
|
|
1490
|
+
effect: "approve"
|
|
1491
|
+
}
|
|
1492
|
+
],
|
|
1493
|
+
artifacts: [
|
|
1494
|
+
{
|
|
1495
|
+
key: config.artifactKey,
|
|
1496
|
+
title: config.artifactTitle,
|
|
1497
|
+
description: config.artifactDescription,
|
|
1498
|
+
kind: config.artifactKind
|
|
1499
|
+
}
|
|
1500
|
+
]
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function cloneGraph(graph) {
|
|
1504
|
+
const cloned = {
|
|
1505
|
+
...(typeof graph.version === "number" ? { version: graph.version } : {}),
|
|
1506
|
+
domain: cloneValue(graph.domain),
|
|
1507
|
+
...(graph.packs ? { packs: cloneValue(graph.packs) } : {}),
|
|
1508
|
+
resources: cloneValue(graph.resources),
|
|
1509
|
+
capabilities: cloneValue(graph.capabilities),
|
|
1510
|
+
tasks: cloneValue(graph.tasks ?? []),
|
|
1511
|
+
policies: cloneValue(graph.policies ?? []),
|
|
1512
|
+
artifacts: cloneValue(graph.artifacts ?? []),
|
|
1513
|
+
views: cloneValue(graph.views ?? [])
|
|
1514
|
+
};
|
|
1515
|
+
if (graph[PACKS_APPLIED]) {
|
|
1516
|
+
cloned[PACKS_APPLIED] = true;
|
|
1517
|
+
}
|
|
1518
|
+
return cloned;
|
|
1519
|
+
}
|
|
1520
|
+
function cloneOptions(options) {
|
|
1521
|
+
return cloneValue(options);
|
|
1522
|
+
}
|
|
1523
|
+
function cloneValue(value) {
|
|
1524
|
+
return JSON.parse(JSON.stringify(value));
|
|
1525
|
+
}
|
|
1526
|
+
function markPacksApplied(graph) {
|
|
1527
|
+
graph[PACKS_APPLIED] = true;
|
|
1528
|
+
return graph;
|
|
1529
|
+
}
|
|
1530
|
+
function readOptionString(selection, key) {
|
|
1531
|
+
const value = selection.options?.[key];
|
|
1532
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1533
|
+
}
|
|
1534
|
+
function pluralize(value) {
|
|
1535
|
+
const trimmed = value.trim();
|
|
1536
|
+
if (trimmed.endsWith("s")) {
|
|
1537
|
+
return `${trimmed}es`;
|
|
1538
|
+
}
|
|
1539
|
+
if (trimmed.endsWith("y") && trimmed.length > 1) {
|
|
1540
|
+
return `${trimmed.slice(0, -1)}ies`;
|
|
1541
|
+
}
|
|
1542
|
+
return `${trimmed}s`;
|
|
1543
|
+
}
|
|
1544
|
+
function toPackKey(value) {
|
|
1545
|
+
const parts = value
|
|
1546
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
1547
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
1548
|
+
.trim()
|
|
1549
|
+
.split(/\s+/)
|
|
1550
|
+
.filter(Boolean);
|
|
1551
|
+
if (!parts.length) {
|
|
1552
|
+
return "packResource";
|
|
1553
|
+
}
|
|
1554
|
+
const [first, ...rest] = parts;
|
|
1555
|
+
return [
|
|
1556
|
+
first.toLowerCase(),
|
|
1557
|
+
...rest.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
1558
|
+
].join("");
|
|
1559
|
+
}
|
|
1560
|
+
function toPackPascalName(value) {
|
|
1561
|
+
const parts = value
|
|
1562
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
1563
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
1564
|
+
.trim()
|
|
1565
|
+
.split(/\s+/)
|
|
1566
|
+
.filter(Boolean);
|
|
1567
|
+
if (!parts.length) {
|
|
1568
|
+
return "PackEntity";
|
|
1569
|
+
}
|
|
1570
|
+
return parts
|
|
1571
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
1572
|
+
.join("");
|
|
1573
|
+
}
|
|
1574
|
+
//# sourceMappingURL=index.js.map
|