@owlmeans/planning 0.1.18-rc.8 → 0.1.18
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/README.md +122 -29
- package/agent-meta/manifest.json +131 -4
- package/agent-meta/skills/planning/SKILL.md +78 -171
- package/agent-meta/skills/planning/parts/collaboration.md +67 -0
- package/agent-meta/skills/planning/parts/errors.md +51 -0
- package/agent-meta/skills/planning/parts/fold.md +69 -0
- package/agent-meta/skills/planning/parts/mounting.md +64 -0
- package/agent-meta/skills/planning/parts/types.md +141 -0
- package/build/aliases/types.d.ts +72 -0
- package/build/aliases/types.d.ts.map +1 -0
- package/build/aliases/types.js +2 -0
- package/build/aliases/types.js.map +1 -0
- package/build/aliases.d.ts +4 -0
- package/build/aliases.d.ts.map +1 -0
- package/build/aliases.js +28 -0
- package/build/aliases.js.map +1 -0
- package/build/consts.d.ts +43 -34
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +49 -25
- package/build/consts.js.map +1 -1
- package/build/entrypoints/types.d.ts +105 -0
- package/build/entrypoints/types.d.ts.map +1 -0
- package/build/entrypoints/types.js +2 -0
- package/build/entrypoints/types.js.map +1 -0
- package/build/entrypoints.d.ts +5 -1
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +24 -6
- package/build/entrypoints.js.map +1 -1
- package/build/errors.d.ts +41 -0
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +61 -0
- package/build/errors.js.map +1 -1
- package/build/helpers/app/consts.local.d.ts +5 -0
- package/build/helpers/app/consts.local.d.ts.map +1 -0
- package/build/helpers/app/consts.local.js +5 -0
- package/build/helpers/app/consts.local.js.map +1 -0
- package/build/helpers/app/types.d.ts +53 -0
- package/build/helpers/app/types.d.ts.map +1 -0
- package/build/helpers/app/types.js +2 -0
- package/build/helpers/app/types.js.map +1 -0
- package/build/helpers/app.d.ts +4 -0
- package/build/helpers/app.d.ts.map +1 -0
- package/build/helpers/app.js +139 -0
- package/build/helpers/app.js.map +1 -0
- package/build/helpers/apply/consts.local.d.ts +7 -0
- package/build/helpers/apply/consts.local.d.ts.map +1 -0
- package/build/helpers/apply/consts.local.js +9 -0
- package/build/helpers/apply/consts.local.js.map +1 -0
- package/build/helpers/apply/types.d.ts +32 -0
- package/build/helpers/apply/types.d.ts.map +1 -0
- package/build/helpers/apply/types.js +2 -0
- package/build/helpers/apply/types.js.map +1 -0
- package/build/helpers/apply.d.ts +5 -23
- package/build/helpers/apply.d.ts.map +1 -1
- package/build/helpers/apply.js +127 -144
- package/build/helpers/apply.js.map +1 -1
- package/build/helpers/card/types.d.ts +20 -0
- package/build/helpers/card/types.d.ts.map +1 -0
- package/build/helpers/card/types.js +2 -0
- package/build/helpers/card/types.js.map +1 -0
- package/build/helpers/card.d.ts +11 -7
- package/build/helpers/card.d.ts.map +1 -1
- package/build/helpers/card.js +41 -28
- package/build/helpers/card.js.map +1 -1
- package/build/helpers/changes/consts.local.d.ts +13 -0
- package/build/helpers/changes/consts.local.d.ts.map +1 -0
- package/build/helpers/changes/consts.local.js +13 -0
- package/build/helpers/changes/consts.local.js.map +1 -0
- package/build/helpers/changes/types.d.ts +58 -0
- package/build/helpers/changes/types.d.ts.map +1 -0
- package/build/helpers/changes/types.js +2 -0
- package/build/helpers/changes/types.js.map +1 -0
- package/build/helpers/changes.d.ts +11 -42
- package/build/helpers/changes.d.ts.map +1 -1
- package/build/helpers/changes.js +237 -251
- package/build/helpers/changes.js.map +1 -1
- package/build/helpers/code/types.d.ts +27 -0
- package/build/helpers/code/types.d.ts.map +1 -0
- package/build/helpers/code/types.js +2 -0
- package/build/helpers/code/types.js.map +1 -0
- package/build/helpers/code.d.ts +6 -17
- package/build/helpers/code.d.ts.map +1 -1
- package/build/helpers/code.js +39 -40
- package/build/helpers/code.js.map +1 -1
- package/build/helpers/index.d.ts +16 -0
- package/build/helpers/index.d.ts.map +1 -1
- package/build/helpers/index.js +4 -0
- package/build/helpers/index.js.map +1 -1
- package/build/helpers/query/types.d.ts +23 -0
- package/build/helpers/query/types.d.ts.map +1 -0
- package/build/helpers/query/types.js +2 -0
- package/build/helpers/query/types.js.map +1 -0
- package/build/helpers/query.d.ts +25 -17
- package/build/helpers/query.d.ts.map +1 -1
- package/build/helpers/query.js +108 -280
- package/build/helpers/query.js.map +1 -1
- package/build/helpers/reply/consts.d.ts +2 -0
- package/build/helpers/reply/consts.d.ts.map +1 -0
- package/build/helpers/reply/consts.js +2 -0
- package/build/helpers/reply/consts.js.map +1 -0
- package/build/helpers/reply/types.d.ts +10 -0
- package/build/helpers/reply/types.d.ts.map +1 -0
- package/build/helpers/reply/types.js +2 -0
- package/build/helpers/reply/types.js.map +1 -0
- package/build/helpers/reply.d.ts +5 -0
- package/build/helpers/reply.d.ts.map +1 -0
- package/build/helpers/reply.js +29 -0
- package/build/helpers/reply.js.map +1 -0
- package/build/helpers/scoped/types.d.ts +73 -0
- package/build/helpers/scoped/types.d.ts.map +1 -0
- package/build/helpers/scoped/types.js +2 -0
- package/build/helpers/scoped/types.js.map +1 -0
- package/build/helpers/scoped.d.ts +25 -0
- package/build/helpers/scoped.d.ts.map +1 -0
- package/build/helpers/scoped.js +223 -0
- package/build/helpers/scoped.js.map +1 -0
- package/build/helpers/specification/types.d.ts +24 -0
- package/build/helpers/specification/types.d.ts.map +1 -0
- package/build/helpers/specification/types.js +2 -0
- package/build/helpers/specification/types.js.map +1 -0
- package/build/helpers/specification.d.ts +9 -13
- package/build/helpers/specification.d.ts.map +1 -1
- package/build/helpers/specification.js +47 -44
- package/build/helpers/specification.js.map +1 -1
- package/build/helpers/status/types.d.ts +48 -0
- package/build/helpers/status/types.d.ts.map +1 -0
- package/build/helpers/status/types.js +2 -0
- package/build/helpers/status/types.js.map +1 -0
- package/build/helpers/status.d.ts +16 -29
- package/build/helpers/status.d.ts.map +1 -1
- package/build/helpers/status.js +77 -73
- package/build/helpers/status.js.map +1 -1
- package/build/helpers/types.local.d.ts +17 -0
- package/build/helpers/types.local.d.ts.map +1 -0
- package/build/helpers/types.local.js +2 -0
- package/build/helpers/types.local.js.map +1 -0
- package/build/helpers/validate/types.d.ts +31 -0
- package/build/helpers/validate/types.d.ts.map +1 -0
- package/build/helpers/validate/types.js +2 -0
- package/build/helpers/validate/types.js.map +1 -0
- package/build/helpers/validate.d.ts +8 -19
- package/build/helpers/validate.d.ts.map +1 -1
- package/build/helpers/validate.js +79 -79
- package/build/helpers/validate.js.map +1 -1
- package/build/helpers/wire/types.d.ts +28 -0
- package/build/helpers/wire/types.d.ts.map +1 -0
- package/build/helpers/wire/types.js +2 -0
- package/build/helpers/wire/types.js.map +1 -0
- package/build/helpers/wire.d.ts +4 -0
- package/build/helpers/wire.d.ts.map +1 -0
- package/build/helpers/wire.js +216 -0
- package/build/helpers/wire.js.map +1 -0
- package/build/i18n/be.json +5 -0
- package/build/i18n/de.json +5 -0
- package/build/i18n/en.json +5 -0
- package/build/i18n/es.json +5 -0
- package/build/i18n/fr.json +5 -0
- package/build/i18n/pl.json +5 -0
- package/build/i18n/ru.json +5 -0
- package/build/i18n/uk.json +5 -0
- package/build/i18n.js +3 -3
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/models/index.d.ts +2 -3
- package/build/models/index.d.ts.map +1 -1
- package/build/models/index.js +1 -15
- package/build/models/index.js.map +1 -1
- package/build/models/model.d.ts +5 -0
- package/build/models/model.d.ts.map +1 -0
- package/build/models/model.js +16 -0
- package/build/models/model.js.map +1 -0
- package/build/models/project.d.ts +2 -1
- package/build/models/project.d.ts.map +1 -1
- package/build/models/project.js.map +1 -1
- package/build/models/specification.d.ts +2 -1
- package/build/models/specification.d.ts.map +1 -1
- package/build/models/specification.js.map +1 -1
- package/build/models/types.d.ts +69 -0
- package/build/models/types.d.ts.map +1 -0
- package/build/models/types.js +2 -0
- package/build/models/types.js.map +1 -0
- package/build/models/workcard.d.ts +2 -1
- package/build/models/workcard.d.ts.map +1 -1
- package/build/models/workcard.js +12 -12
- package/build/models/workcard.js.map +1 -1
- package/build/registry.d.ts +3 -2
- package/build/registry.d.ts.map +1 -1
- package/build/registry.js +51 -3
- package/build/registry.js.map +1 -1
- package/build/resources/mentions.d.ts +4 -0
- package/build/resources/mentions.d.ts.map +1 -0
- package/build/resources/mentions.js +15 -0
- package/build/resources/mentions.js.map +1 -0
- package/build/resources/protocol/types.d.ts +30 -0
- package/build/resources/protocol/types.d.ts.map +1 -0
- package/build/resources/protocol/types.js +2 -0
- package/build/resources/protocol/types.js.map +1 -0
- package/build/resources/protocol.d.ts +6 -0
- package/build/resources/protocol.d.ts.map +1 -0
- package/build/resources/protocol.js +31 -0
- package/build/resources/protocol.js.map +1 -0
- package/build/resources/types.d.ts +146 -0
- package/build/resources/types.d.ts.map +1 -0
- package/build/resources/types.js +2 -0
- package/build/resources/types.js.map +1 -0
- package/build/resources/types.local.d.ts +6 -0
- package/build/resources/types.local.d.ts.map +1 -0
- package/build/resources/types.local.js +2 -0
- package/build/resources/types.local.js.map +1 -0
- package/build/schemas.d.ts +21 -2
- package/build/schemas.d.ts.map +1 -1
- package/build/schemas.js +164 -2
- package/build/schemas.js.map +1 -1
- package/build/types.d.ts +330 -151
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +6 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +6 -0
- package/build/utils.js.map +1 -0
- package/package.json +13 -13
- package/src/aliases/types.ts +41 -0
- package/src/aliases.ts +34 -0
- package/src/consts.ts +50 -25
- package/src/entrypoints/types.ts +77 -0
- package/src/entrypoints.ts +53 -24
- package/src/errors.ts +71 -0
- package/src/helpers/app/consts.local.ts +5 -0
- package/src/helpers/app/types.ts +54 -0
- package/src/helpers/app.ts +158 -0
- package/src/helpers/apply/consts.local.ts +10 -0
- package/src/helpers/apply/types.ts +32 -0
- package/src/helpers/apply.ts +141 -153
- package/src/helpers/card/types.ts +20 -0
- package/src/helpers/card.ts +59 -33
- package/src/helpers/changes/consts.local.ts +17 -0
- package/src/helpers/changes/types.ts +66 -0
- package/src/helpers/changes.ts +270 -280
- package/src/helpers/code/types.ts +31 -0
- package/src/helpers/code.ts +52 -48
- package/src/helpers/index.ts +17 -0
- package/src/helpers/query/types.ts +26 -0
- package/src/helpers/query.ts +144 -314
- package/src/helpers/reply/consts.ts +1 -0
- package/src/helpers/reply/types.ts +13 -0
- package/src/helpers/reply.ts +27 -0
- package/src/helpers/scoped/types.ts +78 -0
- package/src/helpers/scoped.ts +281 -0
- package/src/helpers/specification/types.ts +26 -0
- package/src/helpers/specification.ts +69 -51
- package/src/helpers/status/types.ts +46 -0
- package/src/helpers/status.ts +117 -93
- package/src/helpers/types.local.ts +18 -0
- package/src/helpers/validate/types.ts +31 -0
- package/src/helpers/validate.ts +89 -77
- package/src/helpers/wire/types.ts +31 -0
- package/src/helpers/wire.ts +252 -0
- package/src/i18n/be.json +5 -0
- package/src/i18n/de.json +5 -0
- package/src/i18n/en.json +5 -0
- package/src/i18n/es.json +5 -0
- package/src/i18n/fr.json +5 -0
- package/src/i18n/pl.json +5 -0
- package/src/i18n/ru.json +5 -0
- package/src/i18n/uk.json +5 -0
- package/src/i18n.ts +4 -3
- package/src/index.ts +8 -0
- package/src/models/index.ts +2 -17
- package/src/models/model.ts +18 -0
- package/src/models/project.ts +2 -1
- package/src/models/specification.ts +2 -1
- package/src/models/types.ts +70 -0
- package/src/models/workcard.ts +14 -16
- package/src/registry.ts +55 -4
- package/src/resources/mentions.ts +11 -0
- package/src/resources/protocol/types.ts +21 -0
- package/src/resources/protocol.ts +36 -0
- package/src/resources/types.local.ts +5 -0
- package/src/resources/types.ts +148 -0
- package/src/schemas.ts +179 -4
- package/src/types.ts +334 -130
- package/src/utils.ts +9 -0
- package/tests/app.spec.ts +188 -0
- package/tests/apply.spec.ts +95 -31
- package/tests/code.spec.ts +9 -9
- package/tests/csp.spec.ts +122 -0
- package/tests/entrypoints.spec.ts +18 -2
- package/tests/error-status.spec.ts +6 -1
- package/tests/models.spec.ts +24 -16
- package/tests/query.spec.ts +13 -15
- package/tests/schemas.spec.ts +8 -8
- package/tests/scoped.spec.ts +161 -0
- package/tests/status.spec.ts +31 -22
package/src/errors.ts
CHANGED
|
@@ -206,6 +206,72 @@ export class PlanningScopeMismatch extends PlanningError {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* A data-defined schema write lost its compare-and-set: the layer's record is not at the version
|
|
211
|
+
* the write replaces. Re-read the layer and retry.
|
|
212
|
+
*/
|
|
213
|
+
export class SchemaConflict extends PlanningError {
|
|
214
|
+
public static override typeName: string = `${PlanningError.typeName}SchemaConflict`
|
|
215
|
+
/** The stored record moved past the expected version: answered 409. */
|
|
216
|
+
public static httpStatus = 409
|
|
217
|
+
|
|
218
|
+
constructor(message: string = 'error') {
|
|
219
|
+
super(`schema-conflict:${message}`)
|
|
220
|
+
this.type = SchemaConflict.typeName
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** A flow cannot be retired while a live type of an affected layer still resolves it. */
|
|
225
|
+
export class SchemaInUse extends PlanningError {
|
|
226
|
+
public static override typeName: string = `${PlanningError.typeName}SchemaInUse`
|
|
227
|
+
/** Other declarations still depend on the key: answered 409. */
|
|
228
|
+
public static httpStatus = 409
|
|
229
|
+
|
|
230
|
+
constructor(message: string = 'error') {
|
|
231
|
+
super(`schema-in-use:${message}`)
|
|
232
|
+
this.type = SchemaInUse.typeName
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The key is registered in code and does not declare itself `overridable` — or it is a project
|
|
238
|
+
* or specification type, which only code declares.
|
|
239
|
+
*/
|
|
240
|
+
export class SchemaSealed extends PlanningError {
|
|
241
|
+
public static override typeName: string = `${PlanningError.typeName}SchemaSealed`
|
|
242
|
+
/** The key cannot be declared as data: answered 422. */
|
|
243
|
+
public static httpStatus = 422
|
|
244
|
+
|
|
245
|
+
constructor(message: string = 'error') {
|
|
246
|
+
super(`schema-sealed:${message}`)
|
|
247
|
+
this.type = SchemaSealed.typeName
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A data-defined declaration fails its closed-form checks. */
|
|
252
|
+
export class SchemaInvalid extends PlanningError {
|
|
253
|
+
public static override typeName: string = `${PlanningError.typeName}SchemaInvalid`
|
|
254
|
+
/** The declaration's content is refused: answered 422. */
|
|
255
|
+
public static httpStatus = 422
|
|
256
|
+
|
|
257
|
+
constructor(message: string = 'error') {
|
|
258
|
+
super(`schema-invalid:${message}`)
|
|
259
|
+
this.type = SchemaInvalid.typeName
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** The request's access does not grant the write (creating or deleting a project, defining schemas). */
|
|
264
|
+
export class PlanningForbidden extends PlanningError {
|
|
265
|
+
public static override typeName: string = `${PlanningError.typeName}Forbidden`
|
|
266
|
+
/** The caller lacks the grant the write needs: answered 403. */
|
|
267
|
+
public static httpStatus = 403
|
|
268
|
+
|
|
269
|
+
constructor(message: string = 'error') {
|
|
270
|
+
super(`forbidden:${message}`)
|
|
271
|
+
this.type = PlanningForbidden.typeName
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
209
275
|
/** The store behind this type cannot do what was asked (a foreign provider without a log). */
|
|
210
276
|
export class PlanningUnsupported extends PlanningError {
|
|
211
277
|
public static override typeName: string = `${PlanningError.typeName}Unsupported`
|
|
@@ -234,4 +300,9 @@ ResilientError.registerErrorClass(WorkcardConflict)
|
|
|
234
300
|
ResilientError.registerErrorClass(CommitTimeout)
|
|
235
301
|
ResilientError.registerErrorClass(CommitFailed)
|
|
236
302
|
ResilientError.registerErrorClass(PlanningScopeMismatch)
|
|
303
|
+
ResilientError.registerErrorClass(SchemaConflict)
|
|
304
|
+
ResilientError.registerErrorClass(SchemaInUse)
|
|
305
|
+
ResilientError.registerErrorClass(SchemaSealed)
|
|
306
|
+
ResilientError.registerErrorClass(SchemaInvalid)
|
|
307
|
+
ResilientError.registerErrorClass(PlanningForbidden)
|
|
237
308
|
ResilientError.registerErrorClass(PlanningUnsupported)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The kinds an application declares properties for, in merge order. */
|
|
2
|
+
export const APP_KINDS = ['type', 'flow', 'card'] as const
|
|
3
|
+
|
|
4
|
+
/** The only keywords a kind's declaration may carry — a merge by name honours nothing else. */
|
|
5
|
+
export const APP_SCHEMA_KEYS = new Set(['type', 'properties', 'required'])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AnySchema } from 'ajv'
|
|
2
|
+
import type { PlanningSchemaKind } from '../../consts.js'
|
|
3
|
+
import type { AnyTypeSchema, PlanningAppDeclarations } from '../../types.js'
|
|
4
|
+
|
|
5
|
+
/** The declarations whose properties a definition carries in its `app` bag. */
|
|
6
|
+
export type PlanningAppBagKind = PlanningSchemaKind.Type | PlanningSchemaKind.Flow
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The application's pregenerated planning properties: the `app` bag of every type and flow
|
|
10
|
+
* declaration, and the base `fields` of every card — declared in code, beside the custom fields a
|
|
11
|
+
* data-defined type gives its cards.
|
|
12
|
+
*/
|
|
13
|
+
export interface AppSchemaHelper {
|
|
14
|
+
/**
|
|
15
|
+
* Merge declarations in the order given — a planning service passes its own `schemas.app` first,
|
|
16
|
+
* then every plugin's in plugin order (`order` ascending, registration order among equals). Per
|
|
17
|
+
* kind (`type`, `flow`, `card`): `properties` are united, a later declaration of a name replacing
|
|
18
|
+
* the earlier one whole; `required` is the union. `undefined` when no source declares anything.
|
|
19
|
+
*
|
|
20
|
+
* @throws {SchemaInvalid} a malformed source
|
|
21
|
+
*/
|
|
22
|
+
merge: (sources: ReadonlyArray<PlanningAppDeclarations | undefined>) => PlanningAppDeclarations | undefined
|
|
23
|
+
/**
|
|
24
|
+
* The closed form of declarations: per kind only `type` (`'object'`), `properties` (names without
|
|
25
|
+
* `.` or `$`) and `required` (each name declared), and — outside a browser document — a schema that
|
|
26
|
+
* compiles. A page never compiles: a strict `script-src` refuses code generation, and what a page
|
|
27
|
+
* registers its server compiled when it registered it.
|
|
28
|
+
*
|
|
29
|
+
* @throws {SchemaInvalid} `app:<kind>:…`
|
|
30
|
+
*/
|
|
31
|
+
assertDeclarations: (app: PlanningAppDeclarations) => void
|
|
32
|
+
/**
|
|
33
|
+
* The schema a definition's `app` bag must match: the declared kind closed over its properties
|
|
34
|
+
* (`additionalProperties: false`). `undefined` when the kind is not declared.
|
|
35
|
+
*/
|
|
36
|
+
bagSchemaOf: (app: PlanningAppDeclarations | undefined, kind: PlanningAppBagKind) => AnySchema | undefined
|
|
37
|
+
/**
|
|
38
|
+
* Check a declaration's `app` bag: refused when the kind is undeclared and the bag is present;
|
|
39
|
+
* matched against {@link bagSchemaOf} otherwise (an absent bag is `{}`, so a required property
|
|
40
|
+
* makes the bag mandatory).
|
|
41
|
+
*
|
|
42
|
+
* @throws {SchemaInvalid} `<kind>:<key>:app:…`
|
|
43
|
+
*/
|
|
44
|
+
assertBag: (kind: PlanningAppBagKind, key: string, bag: unknown, app?: PlanningAppDeclarations) => void
|
|
45
|
+
/**
|
|
46
|
+
* The schema a type's `fields` validate against. Without base card fields, or for a project or
|
|
47
|
+
* specification type, it is the type's own `fields` object itself. Otherwise the type's schema
|
|
48
|
+
* stays the root (so its `#/…` references still resolve) and gains: each base name in its
|
|
49
|
+
* top-level `properties` as `{}` when it does not declare it (its `additionalProperties` never
|
|
50
|
+
* refuses a base field; its own declaration of the name still applies and may narrow it), and the
|
|
51
|
+
* base object schema appended to its `allOf`.
|
|
52
|
+
*/
|
|
53
|
+
fieldsSchemaOf: (type: AnyTypeSchema, app?: PlanningAppDeclarations) => AnySchema
|
|
54
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { AnySchema, ValidateFunction } from 'ajv'
|
|
2
|
+
import { PlanningSchemaKind, WorkcardKind } from '../consts.js'
|
|
3
|
+
import { SchemaInvalid } from '../errors.js'
|
|
4
|
+
import type { PlanningAppDeclarations, PlanningAppSchema } from '../types.js'
|
|
5
|
+
import type { AppSchemaHelper, PlanningAppBagKind } from './app/types.js'
|
|
6
|
+
import { APP_KINDS, APP_SCHEMA_KEYS } from './app/consts.local.js'
|
|
7
|
+
import { makeAjv, validateHelper } from './validate.js'
|
|
8
|
+
|
|
9
|
+
const messageOf = (error: unknown): string => error instanceof Error ? error.message : `${error}`
|
|
10
|
+
|
|
11
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
12
|
+
value != null && typeof value === 'object' && !Array.isArray(value)
|
|
13
|
+
|
|
14
|
+
export const createAppSchemaHelper = (): AppSchemaHelper => {
|
|
15
|
+
// Keyed by the declared kind object a registry keeps — a fresh ajv per entry, so a replaced
|
|
16
|
+
// declaration leaves nothing compiled behind.
|
|
17
|
+
const bags = new WeakMap<PlanningAppSchema, ValidateFunction>()
|
|
18
|
+
|
|
19
|
+
// A page never compiles: under a strict `script-src` Ajv's code generation is refused, and the
|
|
20
|
+
// declarations a page registers are its server's, compiled there when they were registered.
|
|
21
|
+
const compiles = (): boolean => typeof (globalThis as { document?: unknown }).document === 'undefined'
|
|
22
|
+
|
|
23
|
+
const objectSchemaOf = (declared: PlanningAppSchema): Record<string, unknown> => ({
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: declared.properties,
|
|
26
|
+
...((declared.required?.length ?? 0) > 0 ? { required: [...declared.required!] } : {}),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const declaredOf = (app: PlanningAppDeclarations | undefined, kind: PlanningAppBagKind): PlanningAppSchema | undefined =>
|
|
30
|
+
kind === PlanningSchemaKind.Type ? app?.type : app?.flow
|
|
31
|
+
|
|
32
|
+
const assertDeclarations: AppSchemaHelper['assertDeclarations'] = app => {
|
|
33
|
+
if (!isObject(app)) {
|
|
34
|
+
throw new SchemaInvalid('app:declarations')
|
|
35
|
+
}
|
|
36
|
+
const kinds: readonly string[] = APP_KINDS
|
|
37
|
+
const stranger = Object.keys(app).find(key => !kinds.includes(key))
|
|
38
|
+
if (stranger != null) {
|
|
39
|
+
throw new SchemaInvalid(`app:kind:${stranger}`)
|
|
40
|
+
}
|
|
41
|
+
for (const kind of APP_KINDS) {
|
|
42
|
+
const declared: unknown = app[kind]
|
|
43
|
+
if (declared == null) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
if (!isObject(declared)) {
|
|
47
|
+
throw new SchemaInvalid(`app:${kind}:schema`)
|
|
48
|
+
}
|
|
49
|
+
const keyword = Object.keys(declared).find(key => !APP_SCHEMA_KEYS.has(key))
|
|
50
|
+
if (keyword != null) {
|
|
51
|
+
throw new SchemaInvalid(`app:${kind}:keyword:${keyword}`)
|
|
52
|
+
}
|
|
53
|
+
if (declared.type != null && declared.type !== 'object') {
|
|
54
|
+
throw new SchemaInvalid(`app:${kind}:type`)
|
|
55
|
+
}
|
|
56
|
+
if (!isObject(declared.properties)) {
|
|
57
|
+
throw new SchemaInvalid(`app:${kind}:properties`)
|
|
58
|
+
}
|
|
59
|
+
const names = Object.keys(declared.properties)
|
|
60
|
+
const badName = names.find(name => name === '' || name.includes('.') || name.includes('$'))
|
|
61
|
+
if (badName != null) {
|
|
62
|
+
throw new SchemaInvalid(`app:${kind}:name:${badName}`)
|
|
63
|
+
}
|
|
64
|
+
const required: unknown = declared.required
|
|
65
|
+
if (required != null && (!Array.isArray(required) || required.some(name => typeof name !== 'string'))) {
|
|
66
|
+
throw new SchemaInvalid(`app:${kind}:required`)
|
|
67
|
+
}
|
|
68
|
+
const missing = (required as string[] | undefined)?.find(name => !names.includes(name))
|
|
69
|
+
if (missing != null) {
|
|
70
|
+
throw new SchemaInvalid(`app:${kind}:required:${missing}`)
|
|
71
|
+
}
|
|
72
|
+
if (compiles()) {
|
|
73
|
+
try {
|
|
74
|
+
makeAjv().compile(objectSchemaOf(declared as unknown as PlanningAppSchema))
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new SchemaInvalid(`app:${kind}:${messageOf(error)}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const merge: AppSchemaHelper['merge'] = sources => {
|
|
83
|
+
const merged: PlanningAppDeclarations = {}
|
|
84
|
+
for (const source of sources) {
|
|
85
|
+
if (source == null) {
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
assertDeclarations(source)
|
|
89
|
+
for (const kind of APP_KINDS) {
|
|
90
|
+
const next = source[kind]
|
|
91
|
+
if (next == null) {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
const current = merged[kind]
|
|
95
|
+
const required = [...new Set([...(current?.required ?? []), ...(next.required ?? [])])]
|
|
96
|
+
merged[kind] = {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: { ...current?.properties, ...structuredClone(next.properties) },
|
|
99
|
+
...(required.length > 0 ? { required } : {}),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Object.keys(merged).length > 0 ? merged : undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const bagSchemaOf: AppSchemaHelper['bagSchemaOf'] = (app, kind) => {
|
|
108
|
+
const declared = declaredOf(app, kind)
|
|
109
|
+
|
|
110
|
+
return declared == null ? undefined : { ...objectSchemaOf(declared), additionalProperties: false }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const assertBag: AppSchemaHelper['assertBag'] = (kind, key, bag, app) => {
|
|
114
|
+
const declared = declaredOf(app, kind)
|
|
115
|
+
if (declared == null) {
|
|
116
|
+
if (bag != null) {
|
|
117
|
+
throw new SchemaInvalid(`${kind}:${key}:app:undeclared`)
|
|
118
|
+
}
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
const keys = validateHelper.invalidFieldKeys(bag)
|
|
122
|
+
if (keys.length > 0) {
|
|
123
|
+
throw new SchemaInvalid(`${kind}:${key}:app:keys:${keys.join(',')}`)
|
|
124
|
+
}
|
|
125
|
+
let validate = bags.get(declared)
|
|
126
|
+
if (validate == null) {
|
|
127
|
+
validate = makeAjv().compile(bagSchemaOf(app, kind) as object)
|
|
128
|
+
bags.set(declared, validate)
|
|
129
|
+
}
|
|
130
|
+
if (!validate(bag ?? {})) {
|
|
131
|
+
throw new SchemaInvalid(`${kind}:${key}:app:${validateHelper.ajvErrorText(validate.errors)}`)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const fieldsSchemaOf: AppSchemaHelper['fieldsSchemaOf'] = (type, app) => {
|
|
136
|
+
const base = app?.card
|
|
137
|
+
const own: AnySchema = type.fields
|
|
138
|
+
if (base == null || type.kind !== WorkcardKind.Card) {
|
|
139
|
+
return own
|
|
140
|
+
}
|
|
141
|
+
const declared = objectSchemaOf(base)
|
|
142
|
+
if (!isObject(own)) {
|
|
143
|
+
return own === false ? false : declared
|
|
144
|
+
}
|
|
145
|
+
// The type's schema stays the root, so its own `#/…` references keep resolving.
|
|
146
|
+
const admitted = Object.fromEntries(Object.keys(base.properties).map(name => [name, {}]))
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
...own,
|
|
150
|
+
properties: { ...admitted, ...(isObject(own.properties) ? own.properties : {}) },
|
|
151
|
+
allOf: [...(Array.isArray(own.allOf) ? own.allOf : []), declared],
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { merge, assertDeclarations, bagSchemaOf, assertBag, fieldsSchemaOf }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const appSchemaHelper = createAppSchemaHelper()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Never written by a transition's `changes`: the fold takes them from the transition itself. */
|
|
2
|
+
export const IDENTITY_KEYS = new Set(['id', 'kind', 'type', 'entityId', 'seq', 'head', 'createdAt'])
|
|
3
|
+
|
|
4
|
+
/** Cannot be cleared: a record without them is not a record. */
|
|
5
|
+
export const REQUIRED_KEYS = new Set([
|
|
6
|
+
...IDENTITY_KEYS, 'title', 'parents', 'status', 'intrinsic', 'flows', 'labels', 'fields',
|
|
7
|
+
])
|
|
8
|
+
|
|
9
|
+
/** Merged shallowly into the card's own map rather than replacing it. */
|
|
10
|
+
export const MERGED_KEYS = new Set(['fields', 'flows'])
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Relationship, Transition, Workcard } from '../../types.js'
|
|
2
|
+
|
|
3
|
+
/** The pure fold — the card and its edges after one transition. */
|
|
4
|
+
export interface ApplyHelper {
|
|
5
|
+
/**
|
|
6
|
+
* THE fold: the card after one transition. Pure, total, no I/O — every store (memory, Mongo, a
|
|
7
|
+
* client mirror) folds with this and nothing else, so one log always means one card.
|
|
8
|
+
*
|
|
9
|
+
* - An already-applied transition (`seq <= card.seq`) returns the card unchanged: re-folding is
|
|
10
|
+
* safe.
|
|
11
|
+
* - Anything but the next seq (`card.seq + 1`, or 1 for a create with no card) is refused with
|
|
12
|
+
* `PlanningError('fold:out-of-order')`.
|
|
13
|
+
* - The log is replayed as written: a transition the executor would refuse today (an update naming
|
|
14
|
+
* `createdBy`, appended before that refusal existed) still folds. Guards live at admission.
|
|
15
|
+
* - `create` builds the whole record from `changes` plus the transition's identity; `delete`
|
|
16
|
+
* answers `null`; `link`/`unlink` only move `seq`/`head`/`updatedAt` (the edges are
|
|
17
|
+
* {@link ApplyHelper.applyRelationship}'s); `update`/`transit` write `changes` — `fields` and
|
|
18
|
+
* `flows` merge, everything else replaces — and clear `unset`.
|
|
19
|
+
* - Every result has `seq = transition.seq`, `head = max(head, seq)`, `updatedAt = transition.at`.
|
|
20
|
+
*
|
|
21
|
+
* @throws {PlanningError} `planning:fold:out-of-order:…`
|
|
22
|
+
*/
|
|
23
|
+
applyTransition: <T extends Workcard = Workcard>(card: T | null | undefined, transition: Transition) => T | null
|
|
24
|
+
/**
|
|
25
|
+
* The relationships after one transition. Pure and total, like {@link ApplyHelper.applyTransition}.
|
|
26
|
+
*
|
|
27
|
+
* `create` adds its `links`, `link` adds its `link`, `unlink` removes the matching edge, and a
|
|
28
|
+
* `delete` removes every edge touching the card. An edge that already exists is kept as it is, so
|
|
29
|
+
* re-folding adds nothing. Every other action answers the list unchanged. Always a new array.
|
|
30
|
+
*/
|
|
31
|
+
applyRelationship: (links: Relationship[], transition: Transition) => Relationship[]
|
|
32
|
+
}
|