@dforge-core/metadata 0.0.1 → 0.0.3

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.
@@ -0,0 +1,99 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://dforge.dev/schemas/traits.schema.json",
4
+ "title": "dForge Trait Definitions",
5
+ "description": "Defines entity traits that expand into fields, references, and constraints during module install. Shipped as traits.json at module root.",
6
+ "type": "object",
7
+ "additionalProperties": {
8
+ "$ref": "#/$defs/traitDefinition"
9
+ },
10
+ "$defs": {
11
+ "traitDefinition": {
12
+ "type": "object",
13
+ "description": "A single trait definition",
14
+ "properties": {
15
+ "description": {
16
+ "type": "string",
17
+ "description": "Human-readable description of the trait"
18
+ },
19
+ "includes": {
20
+ "type": "array",
21
+ "items": { "type": "string" },
22
+ "description": "Other trait names to include (inherit fields from). Expanded recursively with circular reference protection."
23
+ },
24
+ "fields": {
25
+ "type": "object",
26
+ "description": "Field definitions to add to entities using this trait. Keys support {entity}/{Entity} placeholders for parametric names.",
27
+ "additionalProperties": {
28
+ "$ref": "#/$defs/traitField"
29
+ }
30
+ },
31
+ "references": {
32
+ "type": "object",
33
+ "description": "FK reference definitions to add. Keys support {Entity} placeholder.",
34
+ "additionalProperties": {
35
+ "$ref": "#/$defs/traitReference"
36
+ }
37
+ },
38
+ "constraints": {
39
+ "type": "object",
40
+ "description": "Constraint definitions to add. Keys support {Entity} placeholder (e.g. 'UQ_{Entity}_period_key')."
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "traitField": {
46
+ "type": "object",
47
+ "description": "Field definition within a trait. Same shape as entity field definitions.",
48
+ "properties": {
49
+ "dbDatatype": { "type": "string" },
50
+ "columnType": { "type": "string" },
51
+ "fieldTypeCd": { "type": "string" },
52
+ "flags": { "type": "string" },
53
+ "isPk": { "type": "boolean" },
54
+ "isIdentity": { "type": "boolean" },
55
+ "isNullable": { "type": "boolean" },
56
+ "formula": { "type": "string" },
57
+ "orderNum": { "type": "integer" },
58
+ "description": { "type": "string", "description": "Supports {entity}/{Entity} placeholders" },
59
+ "maxLen": { "type": "integer" },
60
+ "link": {
61
+ "type": "object",
62
+ "properties": {
63
+ "entity": { "type": "string" },
64
+ "thisKey": { "type": "string" },
65
+ "otherKey": { "type": "string" }
66
+ },
67
+ "required": ["entity", "thisKey", "otherKey"],
68
+ "additionalProperties": false
69
+ }
70
+ },
71
+ "additionalProperties": false
72
+ },
73
+ "traitReference": {
74
+ "type": "object",
75
+ "description": "FK reference definition within a trait",
76
+ "required": ["from", "to"],
77
+ "properties": {
78
+ "from": {
79
+ "type": "object",
80
+ "required": ["field"],
81
+ "properties": {
82
+ "field": { "type": "string" }
83
+ },
84
+ "additionalProperties": false
85
+ },
86
+ "to": {
87
+ "type": "object",
88
+ "required": ["entity", "field"],
89
+ "properties": {
90
+ "entity": { "type": "string" },
91
+ "field": { "type": "string" }
92
+ },
93
+ "additionalProperties": false
94
+ }
95
+ },
96
+ "additionalProperties": false
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://dforge.dev/schemas/triggers.schema.json",
4
+ "title": "dForge Module Triggers",
5
+ "description": "Trigger definitions for a dForge module (logic/triggers.json). A trigger watches an entity for an event (insert/update/delete/status_change/any), evaluates an optional formula condition using `[field]` syntax, and fires an action if true. Different from scheduled jobs (cron-driven) and webhooks (outbound HTTP) — triggers are intra-tenant DB-event-driven action invocations.",
6
+ "type": "object",
7
+ "required": ["triggers"],
8
+ "properties": {
9
+ "triggers": {
10
+ "type": "array",
11
+ "items": { "$ref": "#/$defs/trigger" }
12
+ }
13
+ },
14
+ "additionalProperties": false,
15
+ "$defs": {
16
+ "trigger": {
17
+ "type": "object",
18
+ "required": ["code", "entity", "event", "action"],
19
+ "properties": {
20
+ "code": {
21
+ "type": "string",
22
+ "pattern": "^[a-z][a-z0-9_]*$",
23
+ "description": "Trigger code, unique within the module."
24
+ },
25
+ "description": {
26
+ "type": "string",
27
+ "description": "Human-readable description of what the trigger does and when."
28
+ },
29
+ "entity": {
30
+ "type": "string",
31
+ "description": "Entity whose events this trigger watches. May be cross-module via dot notation (e.g. 'fin.invoice')."
32
+ },
33
+ "event": {
34
+ "type": "string",
35
+ "enum": ["insert", "update", "delete", "status_change", "any"],
36
+ "description": "DB event that fires this trigger. 'status_change' only fires when the entity's status field actually changes value. 'any' fires for all insert/update/delete."
37
+ },
38
+ "condition": {
39
+ "type": "string",
40
+ "description": "Optional formula expression — same shape as canExecute in action DSL. Uses `[field]` syntax to reference the affected record. Single-line, evaluates to boolean. If omitted, trigger fires for every matching event."
41
+ },
42
+ "action": {
43
+ "type": "string",
44
+ "description": "Action code to invoke. The action must NOT use record-context (`[field]`) syntax in a way that requires the original triggering record — the trigger passes the record id via params. Cross-module form 'module.action' is supported."
45
+ },
46
+ "params": {
47
+ "type": "object",
48
+ "description": "Optional static parameters passed to the action at invocation time. Merged with the runtime-injected `record_id` / `record_old` (if event uses old-record diff)."
49
+ },
50
+ "async": {
51
+ "type": "boolean",
52
+ "default": false,
53
+ "description": "When true, the action runs in the background — the triggering transaction commits before the action starts. Recommended for slow actions (emails, external API calls). When false, the action runs in the same transaction; failures roll back the original DB change."
54
+ }
55
+ },
56
+ "additionalProperties": false
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "dForge Module Webhooks",
4
+ "description": "Webhook subscription definitions for a dForge module package (webhooks.json)",
5
+ "type": "object",
6
+ "required": ["subscriptions"],
7
+ "properties": {
8
+ "subscriptions": {
9
+ "type": "array",
10
+ "items": {
11
+ "type": "object",
12
+ "required": ["code", "description", "entity", "event"],
13
+ "properties": {
14
+ "code": {
15
+ "type": "string",
16
+ "maxLength": 100,
17
+ "pattern": "^[a-z][a-z0-9_]*$",
18
+ "description": "Unique identifier within the module"
19
+ },
20
+ "description": {
21
+ "type": "string",
22
+ "description": "Human-readable description"
23
+ },
24
+ "entity": {
25
+ "type": "string",
26
+ "description": "Entity code that triggers this webhook"
27
+ },
28
+ "event": {
29
+ "type": "string",
30
+ "enum": ["insert", "update", "delete", "status_change", "any"],
31
+ "description": "Event type that fires this webhook"
32
+ },
33
+ "condition": {
34
+ "type": "string",
35
+ "description": "Formula evaluated against the record. Webhook fires only if true."
36
+ },
37
+ "endpointUrl": {
38
+ "type": "string",
39
+ "format": "uri",
40
+ "description": "Default endpoint URL. Integration modules ship with this pre-configured; admin can override."
41
+ },
42
+ "secretCd": {
43
+ "type": "string",
44
+ "description": "Secret code for API key (from dforge.secret table, admin-configured)"
45
+ },
46
+ "payload": {
47
+ "type": "object",
48
+ "properties": {
49
+ "include": {
50
+ "type": "array",
51
+ "items": { "type": "string" },
52
+ "description": "Field codes to include. If omitted, include all visible fields."
53
+ },
54
+ "includeOld": {
55
+ "type": "boolean",
56
+ "default": false,
57
+ "description": "Include previous field values on update/status_change events"
58
+ }
59
+ },
60
+ "additionalProperties": false
61
+ },
62
+ "enabled": {
63
+ "type": "boolean",
64
+ "default": true,
65
+ "description": "Can be set to false to disable without removing"
66
+ }
67
+ },
68
+ "additionalProperties": false
69
+ }
70
+ }
71
+ },
72
+ "additionalProperties": false
73
+ }
@@ -0,0 +1,48 @@
1
+ // Entity introspection helpers — derive the projections clients commonly need
2
+ // (primary keys, identity-trait keys, reference links) from a raw EntityDef.
3
+ // Shared so the editor, web app and CLI compute these the same way instead of
4
+ // each reimplementing the rules.
5
+
6
+ import type { EntityDef } from "./entity";
7
+ import { expandTrait } from "./traits";
8
+
9
+ /** `{ field, target }` for each Reference (R) column that declares a link. */
10
+ export function getLinks(entity: EntityDef): Array<{ field: string; target: string }> {
11
+ const links: Array<{ field: string; target: string }> = [];
12
+ for (const [code, field] of Object.entries(entity.fields ?? {})) {
13
+ if (field.columnType === "R" && field.link?.entity) {
14
+ links.push({ field: code, target: field.link.entity });
15
+ }
16
+ }
17
+ return links;
18
+ }
19
+
20
+ /**
21
+ * Identity-trait primary-key column codes for `entityCode` (e.g. `invoice_id`),
22
+ * excluding any the author already declared explicitly. Empty unless the entity
23
+ * carries the `identity` trait.
24
+ */
25
+ export function getIdentityKeys(entity: EntityDef, entityCode: string): string[] {
26
+ if (!entity.traits?.includes("identity")) return [];
27
+ const explicit = new Set(Object.keys(entity.fields ?? {}));
28
+ return Object.keys(expandTrait("identity", entityCode)).filter((key) => !explicit.has(key));
29
+ }
30
+
31
+ /**
32
+ * All primary-key column codes: fields flagged `isPk`, plus identity-trait keys.
33
+ * Order is explicit-first, then identity keys; duplicates removed.
34
+ */
35
+ export function getPrimaryKeys(entity: EntityDef, entityCode: string): string[] {
36
+ const explicit = Object.entries(entity.fields ?? {})
37
+ .filter(([, field]) => field.isPk)
38
+ .map(([code]) => code);
39
+ const seen = new Set(explicit);
40
+ const result = [...explicit];
41
+ for (const key of getIdentityKeys(entity, entityCode)) {
42
+ if (!seen.has(key)) {
43
+ seen.add(key);
44
+ result.push(key);
45
+ }
46
+ }
47
+ return result;
48
+ }
@@ -34,6 +34,7 @@ export const fieldTypes = [
34
34
  { cd: "lookup", name: "Lookup", columnType: "R", baseDatatypeCd: "guid", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: true },
35
35
  { cd: "user", name: "User Picker", columnType: "D", baseDatatypeCd: "cuid", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
36
36
  { cd: "grid", name: "Detail Grid", columnType: "S", baseDatatypeCd: "set", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
37
+ { cd: "list", name: "Detail List", columnType: "S", baseDatatypeCd: "set", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
37
38
  { cd: "tags", name: "Tags", columnType: "D", baseDatatypeCd: "string", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: true },
38
39
  { cd: "entitylink", name: "Entity Link", columnType: "D", baseDatatypeCd: "json", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
39
40
  { cd: "code", name: "Code Editor", columnType: "D", baseDatatypeCd: "string", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
package/src/index.ts CHANGED
@@ -57,6 +57,9 @@ export type {
57
57
  TraitCd,
58
58
  } from "./entity";
59
59
 
60
+ // Entity introspection helpers (primary keys, identity keys, reference links).
61
+ export { getLinks, getIdentityKeys, getPrimaryKeys } from "./entity-meta";
62
+
60
63
  export {
61
64
  dataViewKinds,
62
65
  type DataViewsFile,
package/src/types.ts CHANGED
@@ -63,6 +63,7 @@ export type FieldTypeCd =
63
63
  | "lookup"
64
64
  | "user"
65
65
  | "grid"
66
+ | "list"
66
67
  | "tags"
67
68
  | "entitylink"
68
69
  | "code";