@sparkerp/plugin-sdk 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.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/bundle/blocks.json +12229 -0
  4. package/bundle/catalog.json +32475 -0
  5. package/bundle/docs/applications/hcm/employee-profile.md +144 -0
  6. package/bundle/docs/concepts/artifact-types.md +80 -0
  7. package/bundle/docs/concepts/glossary.md +124 -0
  8. package/bundle/docs/concepts/overview.md +88 -0
  9. package/bundle/docs/concepts/page-data-flow.md +84 -0
  10. package/bundle/docs/concepts/plugin-lifecycle.md +81 -0
  11. package/bundle/docs/concepts/sdk-modes.md +71 -0
  12. package/bundle/docs/docs.json +109 -0
  13. package/bundle/docs/guides/add-a-cadence-job.md +134 -0
  14. package/bundle/docs/guides/add-a-compliance-job.md +139 -0
  15. package/bundle/docs/guides/add-a-create-edit-form.md +167 -0
  16. package/bundle/docs/guides/add-a-custom-block.md +225 -0
  17. package/bundle/docs/guides/add-a-data-provider.md +216 -0
  18. package/bundle/docs/guides/add-a-kpi.md +154 -0
  19. package/bundle/docs/guides/add-a-reminder-job.md +181 -0
  20. package/bundle/docs/guides/add-an-aggregation-job.md +153 -0
  21. package/bundle/docs/guides/add-an-approval-workflow.md +222 -0
  22. package/bundle/docs/guides/add-an-entity.md +170 -0
  23. package/bundle/docs/guides/add-business-rules.md +147 -0
  24. package/bundle/docs/guides/add-i18n.md +121 -0
  25. package/bundle/docs/guides/add-menus.md +149 -0
  26. package/bundle/docs/guides/build-a-code-plugin.md +141 -0
  27. package/bundle/docs/guides/build-a-page.md +213 -0
  28. package/bundle/docs/guides/build-a-polyglot-service-plugin.md +201 -0
  29. package/bundle/docs/guides/build-a-tenant-extension-service.md +149 -0
  30. package/bundle/docs/guides/create-a-plugin.md +172 -0
  31. package/bundle/docs/guides/expose-a-plugin-operation-as-an-ai-tool.md +114 -0
  32. package/bundle/docs/guides/index.md +53 -0
  33. package/bundle/docs/guides/publish-and-upgrade.md +152 -0
  34. package/bundle/docs/guides/responsive-plugin.md +117 -0
  35. package/bundle/docs/guides/seed-data-on-install.md +127 -0
  36. package/bundle/docs/guides/set-up-the-sdk.md +120 -0
  37. package/bundle/docs/guides/use-the-mcp-server.md +104 -0
  38. package/bundle/docs/guides/validate-and-test.md +119 -0
  39. package/bundle/docs/guides/wire-a-pages-data.md +153 -0
  40. package/bundle/docs/quickstart/index.md +118 -0
  41. package/bundle/docs/recipes/condition-based-compliance-flag.md +70 -0
  42. package/bundle/docs/recipes/cross-plugin-offboarding-fanout.md +68 -0
  43. package/bundle/docs/recipes/cross-row-aggregation.md +67 -0
  44. package/bundle/docs/recipes/entity-with-approval-workflow.md +72 -0
  45. package/bundle/docs/recipes/index.md +40 -0
  46. package/bundle/docs/recipes/install-lifecycle-data-seeding.md +76 -0
  47. package/bundle/docs/recipes/kpi-dashboard-page.md +92 -0
  48. package/bundle/docs/recipes/master-detail-page.md +84 -0
  49. package/bundle/docs/recipes/n-days-before-reminder.md +72 -0
  50. package/bundle/docs/recipes/per-row-document-generator.md +65 -0
  51. package/bundle/docs/recipes/related-record-lookup-field.md +74 -0
  52. package/bundle/docs/recipes/resolved-reference-grid-column.md +63 -0
  53. package/bundle/docs/recipes/scheduled-record-generation.md +68 -0
  54. package/bundle/docs/reference/action.md +18 -0
  55. package/bundle/docs/reference/block-definition.md +35 -0
  56. package/bundle/docs/reference/block-instance.md +33 -0
  57. package/bundle/docs/reference/composite-pack-manifest.md +33 -0
  58. package/bundle/docs/reference/connector-definition.md +36 -0
  59. package/bundle/docs/reference/data-service-definition.md +22 -0
  60. package/bundle/docs/reference/data-view-definition.md +26 -0
  61. package/bundle/docs/reference/entity-aggregation-config.md +38 -0
  62. package/bundle/docs/reference/entity-cadence-config.md +33 -0
  63. package/bundle/docs/reference/entity-compliance-config.md +37 -0
  64. package/bundle/docs/reference/entity-cross-plugin-action-config.md +37 -0
  65. package/bundle/docs/reference/entity-definition.md +18 -0
  66. package/bundle/docs/reference/entity-document-generator-config.md +35 -0
  67. package/bundle/docs/reference/entity-rule-definition.md +29 -0
  68. package/bundle/docs/reference/entity-status-date-sweep-config.md +27 -0
  69. package/bundle/docs/reference/form.md +35 -0
  70. package/bundle/docs/reference/index.md +37 -0
  71. package/bundle/docs/reference/menu.md +26 -0
  72. package/bundle/docs/reference/mobile-nav.md +22 -0
  73. package/bundle/docs/reference/page-template.md +29 -0
  74. package/bundle/docs/reference/page.md +44 -0
  75. package/bundle/docs/reference/plugin-manifest.md +54 -0
  76. package/bundle/docs/reference/plugin-seed-data.md +21 -0
  77. package/bundle/docs/reference/print-template-definition.md +22 -0
  78. package/bundle/docs/reference/provider-definition.md +22 -0
  79. package/bundle/docs/reference/report-definition.md +22 -0
  80. package/bundle/docs/reference/rule.md +30 -0
  81. package/bundle/docs/reference/workflow-definition.md +28 -0
  82. package/bundle/docs/troubleshooting/grid-shows-zero-rows.md +82 -0
  83. package/bundle/docs/troubleshooting/job-never-runs.md +89 -0
  84. package/bundle/docs/troubleshooting/known-gotchas.md +193 -0
  85. package/bundle/docs/troubleshooting/publishing-errors.md +119 -0
  86. package/bundle/docs/tutorial/01-create-the-plugin.md +157 -0
  87. package/bundle/docs/tutorial/02-category-entity-and-seed.md +94 -0
  88. package/bundle/docs/tutorial/03-equipment-entity.md +121 -0
  89. package/bundle/docs/tutorial/04-equipment-register-page.md +153 -0
  90. package/bundle/docs/tutorial/05-checkout-entity-and-page.md +116 -0
  91. package/bundle/docs/tutorial/06-checkout-approval-workflow.md +139 -0
  92. package/bundle/docs/tutorial/07-return-due-reminder-job.md +129 -0
  93. package/bundle/docs/tutorial/08-menus-i18n-publish.md +123 -0
  94. package/bundle/docs/tutorial/index.md +72 -0
  95. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-aggregation-configs.json +18 -0
  96. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cadence-configs.json +9 -0
  97. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-compliance-configs.json +74 -0
  98. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cross-plugin-configs.json +9 -0
  99. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-document-configs.json +9 -0
  100. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-sweep-configs.json +260 -0
  101. package/bundle/examples/backend/modules/hcm-compensation/spk-assembly/metadata/entities/hcm_salary_planning_entry.json +41 -0
  102. package/bundle/examples/backend/modules/hcm-compensation/spk-assembly/metadata/page/salary-planning.json +1 -0
  103. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/page/promotions.json +10944 -0
  104. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.movement.json +44 -0
  105. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.promotion-acknowledge.json +19 -0
  106. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.resignation.json +29 -0
  107. package/bundle/examples/example-patterns.mjs +94 -0
  108. package/bundle/manifest.json +14 -0
  109. package/bundle/schemas/action.schema.json +102 -0
  110. package/bundle/schemas/block-definition.schema.json +222 -0
  111. package/bundle/schemas/block-instance.schema.json +139 -0
  112. package/bundle/schemas/composite-pack-manifest.schema.json +160 -0
  113. package/bundle/schemas/connector-definition.schema.json +29 -0
  114. package/bundle/schemas/data-service-definition.schema.json +64 -0
  115. package/bundle/schemas/data-view-definition.schema.json +148 -0
  116. package/bundle/schemas/entity-aggregation-config.schema.json +32 -0
  117. package/bundle/schemas/entity-cadence-config.schema.json +27 -0
  118. package/bundle/schemas/entity-compliance-config.schema.json +31 -0
  119. package/bundle/schemas/entity-cross-plugin-action-config.schema.json +31 -0
  120. package/bundle/schemas/entity-definition.schema.json +126 -0
  121. package/bundle/schemas/entity-document-generator-config.schema.json +29 -0
  122. package/bundle/schemas/entity-rule-definition.schema.json +75 -0
  123. package/bundle/schemas/entity-status-date-sweep-config.schema.json +21 -0
  124. package/bundle/schemas/form.schema.json +281 -0
  125. package/bundle/schemas/menu.schema.json +106 -0
  126. package/bundle/schemas/mobile-nav.schema.json +53 -0
  127. package/bundle/schemas/page-template.schema.json +46 -0
  128. package/bundle/schemas/page.schema.json +292 -0
  129. package/bundle/schemas/plugin-manifest.schema.json +139 -0
  130. package/bundle/schemas/plugin-seed-data.schema.json +24 -0
  131. package/bundle/schemas/print-template-definition.schema.json +90 -0
  132. package/bundle/schemas/provider-definition.schema.json +27 -0
  133. package/bundle/schemas/report-definition.schema.json +96 -0
  134. package/bundle/schemas/rule.schema.json +82 -0
  135. package/bundle/schemas/workflow-definition.schema.json +42 -0
  136. package/bundle/validators/block-engine.mjs +23222 -0
  137. package/bundle/validators/page-engine.mjs +21645 -0
  138. package/erp-cli/authoring-root.mjs +344 -0
  139. package/erp-cli/erp.mjs +4065 -0
  140. package/erp-cli/example-patterns.mjs +94 -0
  141. package/erp-mcp-server/index.mjs +818 -0
  142. package/package.json +27 -0
@@ -0,0 +1,144 @@
1
+ ---
2
+ title: Employee Profile
3
+ audience: tenant
4
+ ---
5
+
6
+ # Employee Profile (HCM)
7
+
8
+ This page documents the shipped **Employee Profile** screen in the HCM
9
+ Employee module (`hcm-employee`), for the people who use it day to day — HR
10
+ admins and anyone with access to an employee record — not for plugin
11
+ developers. If you're building or extending this screen yourself, it's an
12
+ ordinary JSON-first page (`spk-assembly/metadata/page/employee-profile.json`)
13
+ like any other; see [Build a page](../../guides/build-a-page.md) for that.
14
+
15
+ ## Getting there
16
+
17
+ Open it from the **Employees** list (or Active Employees / Consultants /
18
+ Contract Employees / Interns / Probation Employees / Remote Employees / Ex
19
+ Employees — the other roster views in the same module): click any row. The
20
+ URL is `/hcm-employee/employee-profile/<employeeId>` — it always shows one
21
+ employee's record, never a list.
22
+
23
+ ## What the header shows
24
+
25
+ - An icon avatar (initials/icon only — there's no photo field on the
26
+ Employee entity, so no photo ever renders here).
27
+ - The employee's full name, plus a status chip: **Active** (green),
28
+ **Inactive** (default), or **Terminated** (red) — whichever one matches
29
+ the record's `employmentStatus`.
30
+ - Their current position/designation.
31
+ - Employee code, email, and phone, each with an icon.
32
+ - Three info chips: **Department**, **Reports To** (manager name), and
33
+ **Employment Type**.
34
+ - An **Edit Profile** button, top right (see below).
35
+
36
+ ## Editing the record
37
+
38
+ **Edit Profile** opens a dialog pre-filled from the current record. It
39
+ covers: Full Name, Email, Phone, Employment Status, Employment Type,
40
+ Department, Position, Reporting Manager, Date of Birth, Gender, Marital
41
+ Status, Nationality, Languages, and Blood Group — plus an optional
42
+ **Apply Change On** date, for scheduling when an edit should take effect
43
+ rather than applying it immediately. Full Name and Email are required;
44
+ Save is blocked until both are filled in.
45
+
46
+ ## The tabs
47
+
48
+ The profile is organized into tabs across the top. What's real and backed by
49
+ live data, and what's disclosed as not yet built, differs by tab — this
50
+ section says which is which plainly, because the page itself does too (see
51
+ the on-page disclosure notes quoted below).
52
+
53
+ ### Overview
54
+
55
+ The landing tab. A grid of cards:
56
+
57
+ - **About** — date of birth, gender, marital status, with a "View more" that
58
+ expands to also show nationality, languages, and blood group.
59
+ - **Job Information** — employee ID, joining date, department, designation,
60
+ reports-to, grade. (Work Location is deliberately not shown here — see
61
+ [Known gaps](#known-gaps).)
62
+ - **Quick Info** — tenant-defined custom fields for the employee, via the
63
+ platform's generic Custom Field Engine (`entityType: "employee"`). This is
64
+ how country- or tenant-specific identifiers (e.g. statutory IDs) show up
65
+ without being hardcoded into this module — an admin adds them as custom
66
+ field definitions, not code changes.
67
+ - **Reporting Structure** — the employee's direct reports, pulled live from
68
+ the org chart.
69
+ - **Recent Activities** — a real, employee-scoped feed from the platform's
70
+ audit trail.
71
+ - **Skills & Expertise** — a real list of skills, each with a proficiency
72
+ level (Beginner / Intermediate / Advanced / Expert). An **Add Skill**
73
+ control lets you add a new skill name + level to the record.
74
+
75
+ ### Employment / Personal
76
+
77
+ Read-oriented detail tabs breaking the same employment and personal-info
78
+ fields (employment type/status, department, grade, work location, hire
79
+ date, reports-to, designation / date of birth, gender, marital status,
80
+ nationality, languages, blood group) into their own focused views, separate
81
+ from the Overview summary cards.
82
+
83
+ ### Documents
84
+
85
+ Real: lists documents actually attached to the employee record via the
86
+ platform's File Management module, plus their verification status. There's
87
+ no upload control on this page yet — the page's own description flags that
88
+ as a known follow-up (needs a file-upload source wired into the page
89
+ runtime). A **Request Document Verification** action opens a dialog
90
+ (document ID + category: Identity Proof, Address Proof, Education
91
+ Certificate, Employment Contract, Work Authorization, or Other) that routes
92
+ into the platform's approval workflow.
93
+
94
+ ### Compensation
95
+
96
+ Real: shows the employee's current compensation (base salary, currency, pay
97
+ frequency, effective date, status) and their compensation history. A
98
+ **Request Compensation Change** action opens a dialog (base salary,
99
+ currency, pay frequency, effective-from date) that also routes into an
100
+ approval workflow rather than writing the change directly.
101
+
102
+ ### Attendance, Leave, Payroll, Performance, Assets, Notes
103
+
104
+ These six tabs are shown with representative sample content, not live data
105
+ — there is no Attendance, Leave, Payroll, Performance, or Assets module
106
+ installed for this platform yet, and Notes has no backend of its own. This
107
+ is a disclosed, deliberate choice the page makes explicit on-screen (small
108
+ disclosure text), not a silent fabrication. For example, the page's own
109
+ copy says: *"Attendance is not yet tracked for this tenant — no Attendance
110
+ module is installed. Not fabricated."* and *"Leave tracking is not yet
111
+ available for this tenant — no Leave module is installed. Balances shown at
112
+ 0, not fabricated."* Treat anything on these six tabs as a preview of the
113
+ intended shape, not real employee data.
114
+
115
+ ### Requests
116
+
117
+ A small summary tab: how many change requests are on file for this
118
+ employee record (i.e. pending/past edits routed through workflow, such as
119
+ the compensation and document-verification requests above), with a link to
120
+ view them.
121
+
122
+ ## Known gaps
123
+
124
+ - **No photo support.** The header always shows an icon, never an uploaded
125
+ photo — there is no photo field on the Employee entity.
126
+ - **Work Location isn't shown**, even though it's a real field elsewhere in
127
+ HCM. Every read on this page goes through a Data Service (a platform-wide
128
+ rule — no page action calls a raw REST endpoint directly), and Work
129
+ Location would need a bespoke recursive-ancestor lookup that isn't
130
+ expressible as a flat Data View today. Rather than fetch it with a raw
131
+ call and break that rule, the field is left off this page entirely — a
132
+ disclosed gap, not a silent downgrade.
133
+ - **Document upload isn't wired into this page yet** — Documents is
134
+ read-only (list + verification status + a verification *request*), not a
135
+ place to attach a new file. That needs a `FileUploadSource` wired into the
136
+ page runtime.
137
+ - **Attendance / Leave / Payroll / Performance / Assets / Notes have no real
138
+ backend.** See [above](#attendance-leave-payroll-performance-assets-notes).
139
+
140
+ ## Related
141
+
142
+ - [Build a page](../../guides/build-a-page.md) — if you're extending this
143
+ page or building something like it.
144
+ - [How does a page get its data?](../../concepts/page-data-flow.md)
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: What are the artifact types?
3
+ audience: tenant
4
+ ---
5
+
6
+ # What are the artifact types?
7
+
8
+ A plugin is a folder of JSON files. Each file is an **artifact** of one type, and
9
+ each type lives in its own subfolder of `spk-assembly/metadata/`. This page is
10
+ the map: what each one is, and which guide covers it.
11
+
12
+ ## The manifest
13
+
14
+ | File | What it is |
15
+ | --- | --- |
16
+ | `spk-assembly/plugin.json` | the manifest — id, version, `mainClass`, `schemaName`, roles, dependencies. [Create a plugin](../guides/create-a-plugin.md) |
17
+
18
+ ## Data
19
+
20
+ | Folder | Type | One-line description | Guide |
21
+ | --- | --- | --- | --- |
22
+ | `metadata/entities/` | **entity** | a table you declare by its fields; the Entity Engine generates DDL + CRUD REST | [Add an entity](../guides/add-an-entity.md) |
23
+ | `metadata/provider/` | **data provider** | a name → REST base path mapping; a page's `metadata.dataSource` points here | [Add a data provider](../guides/add-a-data-provider.md) |
24
+ | `metadata/data_view/` | **data view** | a declared read-only SQL join over physical tables | [Add a data provider](../guides/add-a-data-provider.md) |
25
+ | `metadata/data_service/` | **data service** | a parameterized query: `count` / `search` / `get` / `composite` | [Add a KPI](../guides/add-a-kpi.md) |
26
+ | `metadata/seed-data/` | **seed data** | rows to upsert on install, idempotently | [Seed data on install](../guides/seed-data-on-install.md) |
27
+
28
+ ## Screens
29
+
30
+ | Folder | Type | One-line description | Guide |
31
+ | --- | --- | --- | --- |
32
+ | `metadata/page/` | **page** | one screen — rows/columns of blocks, plus page-level `metadata.dataSource` | [Build a page](../guides/build-a-page.md) |
33
+ | `metadata/menu/` | **menu** | a tree of navigation nodes pointing at routes | [Add menus](../guides/add-menus.md) |
34
+ | `metadata/i18n/` | **translations** | `en.json` etc. — dotted key → text | [Add i18n](../guides/add-i18n.md) |
35
+ | `metadata/application/` | **application** | (standalone plugins) the top-level product surface | [Tutorial ch.1](../tutorial/01-create-the-plugin.md) |
36
+ | `metadata/module/` | **module** | (standalone plugins) a functional area inside an application; pages attach here | [Tutorial ch.1](../tutorial/01-create-the-plugin.md) |
37
+
38
+ ## Logic
39
+
40
+ | Folder | Type | One-line description | Guide |
41
+ | --- | --- | --- | --- |
42
+ | `metadata/rules/` | **entity rule** | declarative logic at a lifecycle point — reject / set value / execute service / start workflow | [Add business rules](../guides/add-business-rules.md) |
43
+ | `metadata/workflow/` | **workflow** | a multi-stage approval process | [Add an approval workflow](../guides/add-an-approval-workflow.md) |
44
+
45
+ ## Code
46
+
47
+ | Path | What it is |
48
+ | --- | --- |
49
+ | `spk-assembly/lib/*.jar` | **optional** — a compiled `Plugin` subclass and its deps, only if the plugin ships Java extensions. A pure-JSON plugin has no `lib/` and leaves `mainClass` `null`. [Create a plugin](../guides/create-a-plugin.md); for a complete real example pairing this with a custom React component, [Build a plugin with custom React + Java code](../guides/build-a-code-plugin.md) |
50
+
51
+ A custom block — a real React component of your own, no Java required — is
52
+ not a `spk-assembly/metadata/` file at all; it's registered at runtime
53
+ through `@erp/block-engine`/`@erp/block-adapter-mui`. See
54
+ [Add a custom block](../guides/add-a-custom-block.md).
55
+
56
+ ## Scheduled jobs are not their own artifact type
57
+
58
+ A scheduled job is **not** a file you write. It's a row you *seed* into a shared
59
+ platform config entity (`entity_status_date_sweep_config`,
60
+ `entity_aggregation_config`, `entity_cadence_config`, `entity_compliance_config`)
61
+ plus an `AFTER_CREATE` register rule. The generic job engine does the rest. See
62
+ the [job guides](../guides/index.md#scheduled-jobs-no-java).
63
+
64
+ ## The two file shapes
65
+
66
+ Most artifact files use a **wrapper**:
67
+
68
+ ```json
69
+ { "name": "...", "description": "...", "definition": { /* the real contract */ }, "metadata": {}, "modules": [] }
70
+ ```
71
+
72
+ Entities use `{ "entity": {...}, "fields": [...] }`. Pages use the wrapper with
73
+ `definition` holding a `contractVersion: 1` object. `erp schema pull <name>`
74
+ shows you the exact shape for each.
75
+
76
+ ## What to read next
77
+
78
+ - [How does a plugin get installed?](./plugin-lifecycle.md)
79
+ - [How does a page get its data?](./page-data-flow.md)
80
+ - [Glossary](./glossary.md)
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Glossary
3
+ audience: tenant
4
+ ---
5
+
6
+ # Glossary
7
+
8
+ Every term these docs use, defined once. Terms are cross-linked from the pages
9
+ that introduce them.
10
+
11
+ **action chain** — an ordered list of *actions* attached to a block *event*
12
+ (`mounted`, `clicked`, `committed`, `rowClicked`). Actions include `callApi`,
13
+ `setValue`, `showToast`, `downloadFile`, `navigate`.
14
+
15
+ **application** — the top-level product surface a user opens (e.g. "HCM"). A
16
+ standalone plugin ships its own in `metadata/application/`.
17
+
18
+ **artifact** — one JSON file of one type in `spk-assembly/metadata/`. See
19
+ [artifact types](./artifact-types.md).
20
+
21
+ **authoring bundle** — the offline grounding data (schemas, capability catalog,
22
+ blocks, examples, docs) the SDK reads. Refreshed with `erp env sync`. See
23
+ [SDK modes](./sdk-modes.md).
24
+
25
+ **binding** — a block property that reads from page state:
26
+ `{ "source": "binding", "binding": { "scope": "page", "key": "kpiTotal" } }`.
27
+ The other property form is `{ "source": "static", "value": ... }`.
28
+
29
+ **block** — a UI widget (`core.grid`, `core.button`, `core.kpi-card`,
30
+ `core.lookup`, …). The unit of a page's layout. Never called "widget" in
31
+ platform terminology, but they're the same thing. `erp blocks list`.
32
+
33
+ **composite (Data Service)** — a Data Service that fans out to several other
34
+ Data Services in one call. Response: `{ "results": { "<step>": <envelope> } }`.
35
+
36
+ **connected mode** — running the SDK as a package against a deployed ERP with no
37
+ platform source. See [SDK modes](./sdk-modes.md).
38
+
39
+ **data provider** — a `metadata/provider/*.json` file mapping a `name` to a REST
40
+ `basePath`. A page's `metadata.dataSource` names one.
41
+
42
+ **data service** — a `metadata/data_service/*.json` file: a parameterized query
43
+ (`count` / `search` / `get` / `composite`). Called at
44
+ `/api/v1/data-services/<name>/execute`.
45
+
46
+ **data view** — a `metadata/data_view/*.json` file: a declared read-only SQL
47
+ join over physical tables.
48
+
49
+ **day_offset** — a field on a status-date-sweep config. `0` = compare a date
50
+ against *now*; `N` = compare against *now + N days* (the "N days before"
51
+ reminder). Negative shifts earlier.
52
+
53
+ **entity** — a table you declare by its fields (`metadata/entities/*.json`). The
54
+ Entity Engine generates the DDL, CRUD REST, grid query, and single-record read.
55
+
56
+ **entity rule** — a `metadata/rules/*.json` file: declarative logic
57
+ (`REJECT` / `SET_VALUE` / `EXECUTE_SERVICE` / `START_WORKFLOW`) at a lifecycle
58
+ point (`BEFORE_CREATE`, `AFTER_UPDATE`, …).
59
+
60
+ **`event.new`** — in a `committed` action chain, the value the user just entered
61
+ or picked. **Not** `event.value`. For a `core.lookup`, it's the picked record's
62
+ id.
63
+
64
+ **`.spk`** — the built plugin package. A zip of `spk-assembly/`.
65
+
66
+ **flags** — a pre-stringified JSON field on an entity field definition. Holds
67
+ `{ "reference": { "entityType": "Employee" } }` (turns on `_label` resolution)
68
+ or `{ "formula": "<expression>" }` (computed field, no column).
69
+
70
+ **i18n key** — a dotted string (`office-equipment.equipment-register.title`)
71
+ standing in for user-visible text. Resolved from `metadata/i18n/en.json`.
72
+
73
+ **idempotent-additive** — the install behaviour for entities and seed data:
74
+ re-installing adds missing columns / upserts rows by key, never drops or
75
+ duplicates.
76
+
77
+ **KPI card** — `core.kpi-card`, a block that shows one number bound from page
78
+ state.
79
+
80
+ **mainClass** — the fully-qualified name of a PF4J `Plugin` subclass in
81
+ `spk-assembly/lib/*.jar`. Optional: a pure-JSON plugin leaves it `null` and the
82
+ platform loads it through a built-in generic no-op entry point. Set it only when
83
+ you ship your own Java extensions.
84
+
85
+ **MCP server** — `erp-plugin-language`, the tool server your AI agent calls for
86
+ schemas, blocks, examples, and validation. `erp mcp install`.
87
+
88
+ **metadata.dataSource** — the page-level string that wires a `core.grid` to a
89
+ data provider. Page-level, never on the grid block.
90
+
91
+ **module** — a functional area inside an application (e.g. "Leave Management").
92
+ Owns menu entries. Pages attach to a module via their `modules` array.
93
+
94
+ **page** — one screen (`metadata/page/*.json`): rows → columns → items (blocks),
95
+ plus page-level `metadata`.
96
+
97
+ **page state** — the runtime key/value bag a page reads with bindings and writes
98
+ with `setValue`. Keys are whatever you invent (`kpiTotal`, `formDialogOpen`).
99
+
100
+ **PF4J** — the Java plugin framework the platform uses to load a plugin. A
101
+ pure-JSON plugin is loaded through a built-in generic entry point; you never
102
+ touch PF4J directly.
103
+
104
+ **register rule** — an unconditional `AFTER_CREATE` `EXECUTE_SERVICE` rule on a
105
+ shared config entity that auto-registers a scheduled job on the first seeded
106
+ config row.
107
+
108
+ **role code** — the stable identifier for a role (`OFFICE_EQUIPMENT_MANAGER`).
109
+ Workflows and permissions reference roles by code, never by display name.
110
+
111
+ **schemaName** — the Postgres schema an entity's table routes to, from
112
+ `plugin.json`. The scaffold default is `erp_core`.
113
+
114
+ **seed data** — `metadata/seed-data/*.json`: rows upserted on install by
115
+ `keyFields`. Used for reference data and for shared job-config rows.
116
+
117
+ **status-date-sweep** — the generic scheduled job
118
+ (`engine-entity.status-date-sweep`) that flips a record's status when a date
119
+ column crosses now (± `day_offset`). Configured by seeding an
120
+ `entity_status_date_sweep_config` row.
121
+
122
+ **workflow** — a `metadata/workflow/*.json` file: stages, an approval task, and
123
+ `approvalPermissions`. Started by a `START_WORKFLOW` rule action; the decision is
124
+ written back by a generic callback controller.
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: Platform overview
3
+ audience: tenant
4
+ ---
5
+
6
+ # Platform overview
7
+
8
+ A plugin is a bundle of **JSON metadata** (and optionally a little Java) that the
9
+ platform's generic engines execute. You rarely write CRUD code — you declare
10
+ what you want and an engine does it.
11
+
12
+ ## App → Module → Page
13
+
14
+ - **Application** — the top-level product surface a user opens (e.g. "HCM").
15
+ - **Module** — a functional area inside an app (e.g. "Leave Management"). A
16
+ module owns menu entries.
17
+ - **Page** — one screen. A `metadata/page/*.json` file: rows of **blocks**
18
+ (widgets), plus page-level `metadata` (including `dataSource` — the single
19
+ field that wires a grid to its data; it is *page*-level, never grid-level).
20
+
21
+ ## Entities
22
+
23
+ An **entity** (`metadata/entities/*.json`, Entity Engine) is a table you declare
24
+ by its fields and relationships. The platform generates the DDL, the CRUD REST
25
+ endpoints, grid queries, and single-record reads — zero hand-written persistence
26
+ code. Mark a foreign-key field with `flags.reference.entityType` and reads
27
+ automatically gain a resolved `<field>_label` sibling (id → display name).
28
+
29
+ ## Data providers, data views, data services
30
+
31
+ - **Data Provider** (`metadata/provider/*.json`) — names a REST base path a grid
32
+ or lookup reads from. A page's `metadata.dataSource` must match a provider's
33
+ `name` exactly.
34
+ - **Data View** (`metadata/data_view/*.json`) — a declared multi-table SQL join
35
+ over physical tables, read-only.
36
+ - **Data Service** (`metadata/data_service/*.json`) — a parameterized query
37
+ (filters, `search` params, composite steps) that a `core.lookup` or a report
38
+ calls server-side.
39
+
40
+ Prefer a Data View / Data Service over a bespoke REST endpoint for reads.
41
+
42
+ ## Workflows
43
+
44
+ A **workflow** (`metadata/workflow/*.json`, Workflow Engine) is a multi-stage
45
+ approval process: `stagesJson`, `tasksJson`, `transitionsJson` (each a
46
+ JSON-encoded string) and a top-level `approvalPermissions` array that references
47
+ roles by stable `role.code`. Every approval-bearing state transition should be a
48
+ real workflow instance, not a permission-gated direct action.
49
+
50
+ ## Rules
51
+
52
+ An **entity rule** (`metadata/rules/*.json`, Rule Engine) fires declarative
53
+ conditions/actions at lifecycle points (`BEFORE_CREATE`, `AFTER_UPDATE`, …) —
54
+ computed fields, cross-field validation, "on first row, register this job".
55
+
56
+ ## Themes
57
+
58
+ Theming cascades: platform → application → **user**. A plugin ships theme tokens;
59
+ it never hardcodes colors in page JSON.
60
+
61
+ ## Scheduled jobs
62
+
63
+ You almost never write a job class. The platform ships generic, JSON-configured
64
+ sweep engines:
65
+
66
+ - **status-date-sweep** — flip a field when a date column crosses now (± an
67
+ offset for "N days before" reminders).
68
+ - **aggregation-sweep** — roll up count/sum/avg/min/max into a summary entity.
69
+ - **cadence-generator** — create records on a schedule.
70
+ - **compliance-sweep** — evaluate a boolean expression per row and flag/escalate.
71
+
72
+ Each is one config row. See `erp examples patterns --kind jobs`.
73
+
74
+ ## Install seeding
75
+
76
+ To make config rows (job configs, reference data) travel with the `.spk`
77
+ instead of a manual per-tenant POST, add `metadata/seed-data/*.json`:
78
+ `{ entity, keyFields, source, rows }`. The installer upserts them idempotently
79
+ on every install/upgrade. Schema: `erp schema pull plugin-seed-data`.
80
+
81
+ ## Go deeper
82
+
83
+ - [What are the artifact types?](./artifact-types.md) — the full folder map
84
+ - [How does a plugin get installed?](./plugin-lifecycle.md) — build → upload → pipeline
85
+ - [How do the SDK modes work?](./sdk-modes.md) — monorepo vs connected
86
+ - [How does a page get its data?](./page-data-flow.md) — the three data paths
87
+ - [Glossary](./glossary.md)
88
+ - [The tutorial](../tutorial/index.md) builds all of this into one working module.
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: How does a page get its data?
3
+ audience: tenant
4
+ ---
5
+
6
+ # How does a page get its data?
7
+
8
+ There are exactly three ways data reaches a page. Knowing which is which stops
9
+ the "0 rows, no error" confusion.
10
+
11
+ ## 1. The grid — via `metadata.dataSource`
12
+
13
+ A `core.grid` block has **no** data configuration on it. It renders whatever the
14
+ page's top-level `metadata.dataSource` resolves to:
15
+
16
+ ```
17
+ page.metadata.dataSource ──▶ a metadata/provider/*.json whose `name` matches
18
+ ──▶ that provider's basePath (e.g. /api/v1/entities/oeq_equipment/records)
19
+ ──▶ the Entity Engine serves rows at basePath + /query
20
+ ```
21
+
22
+ The grid's `externalFilter` binding supplies query filters; `refreshTrigger`
23
+ re-runs the query. This is the *only* mechanism for `core.grid`. See
24
+ [Wire a page's data](../guides/wire-a-pages-data.md).
25
+
26
+ ## 2. Page state — via action chains
27
+
28
+ Everything that isn't the grid — KPI numbers, a detail panel, a form's current
29
+ values, dialog open/closed flags — lives in **page state**, a bag of key/value
30
+ pairs. You:
31
+
32
+ - **write** it with `setValue` actions: `setValue page.kpiTotal = ${kpi.results.total.value}`
33
+ - **read** it with bindings: `{ "source": "binding", "binding": { "scope": "page", "key": "kpiTotal" } }`
34
+
35
+ An **action chain** is an ordered list of actions on a block event
36
+ (`mounted`, `clicked`, `committed`, `rowClicked`). The typical loader chain:
37
+
38
+ ```
39
+ mounted:
40
+ callApi POST /api/v1/data-services/oeq-equipment-kpis/execute → output "kpi"
41
+ setValue page.kpiTotal = ${kpi.results.total.value}
42
+ setValue page.kpiAvailable = ${kpi.results.available.value}
43
+ ```
44
+
45
+ `${kpi....}` references the `output` name of an earlier `callApi` in the same
46
+ chain. `${event.new}` (in a `committed` handler) is the value the user entered.
47
+ `${event.id}` (in `rowClicked`) is the clicked row's id.
48
+
49
+ ## 3. A lookup field — via `optionsSourceKey`
50
+
51
+ A `core.lookup` block calls a **`search` Data Service** named by its
52
+ `optionsSourceKey`, passing the user's keystrokes as the `search` parameter, and
53
+ expects back `{ value, label }` rows. Its `committed` event fires with
54
+ `event.new` = the picked record's id. See
55
+ [the lookup recipe](../recipes/related-record-lookup-field.md).
56
+
57
+ ## Data Service response shapes (the part everyone gets wrong once)
58
+
59
+ | `operation` | `/execute` returns | bind |
60
+ | --- | --- | --- |
61
+ | `count` | `{ "value": 12 }` | `${out.value}` |
62
+ | `search` / `get` | `{ "items": [...] }` (or `{ "records": [...] }` for a dataView source) | `${out.items}` |
63
+ | `composite` | `{ "results": { "total": { "value": 12 }, ... } }` | `${out.results.total.value}` |
64
+
65
+ A parameterized Data Service is called with args nested **one level deeper**:
66
+ `{ "params": { "parameters": { "search": "lap" } } }`.
67
+
68
+ ## Putting it together — the tutorial's Equipment Register page
69
+
70
+ ```
71
+ mount loader block → callApi oeq-equipment-kpis → setValue page.kpiTotal / kpiAvailable / kpiAssigned
72
+ KPI cards → bind page.kpiTotal / ...
73
+ search + status inputs → committed → setValue page.gridExternalFilter.search / .status
74
+ grid → page.metadata.dataSource = "oeq-equipment-provider"; externalFilter = page.gridExternalFilter
75
+ "New" button → setValue page.form* = null/defaults; setValue page.formDialogOpen = true
76
+ dialog inputs → committed → setValue page.form<Field> = ${event.new}
77
+ "Save" button → callApi POST /records; setValue page.formDialogOpen=false; setValue page.gridRefreshTick=${!page.gridRefreshTick}
78
+ ```
79
+
80
+ ## What to read next
81
+
82
+ - [Wire a page's data](../guides/wire-a-pages-data.md)
83
+ - [Add a create / edit form](../guides/add-a-create-edit-form.md)
84
+ - [Add a KPI or aggregation](../guides/add-a-kpi.md)
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: How does a plugin get installed?
3
+ audience: tenant
4
+ ---
5
+
6
+ # How does a plugin get installed?
7
+
8
+ A mental model of what happens between `erp plugin publish` and your page being
9
+ live. Knowing this makes install errors readable.
10
+
11
+ ## Build
12
+
13
+ `erp plugin build spk-assembly -o x.spk` zips `spk-assembly/` into a `.spk`
14
+ file. Nothing clever — it's a zip with `plugin.json` at the root, `metadata/`,
15
+ and `lib/`. It runs `erp plugin validate` first and refuses to package invalid
16
+ pages.
17
+
18
+ ## Upload
19
+
20
+ `erp plugin publish x.spk --tenant N` POSTs the file to
21
+ `/api/v1/authoring/plugins/upload` with your `erp login` token. The server:
22
+
23
+ 1. **recomputes the checksum** and compares — it never trusts the client digest.
24
+ 2. **stores the artifact** at `system/plugins/<id>/<version>/<id>-<version>.spk`.
25
+ This key is **immutable** — re-storing different bytes under the same version
26
+ is refused.
27
+ 3. **reads the manifest** — `id` and `version` must be present and non-blank.
28
+ `mainClass` is optional: a pure-JSON plugin leaves it `null` and the platform
29
+ loads it via a built-in generic no-op entry point.
30
+
31
+ ## Install pipeline (`PluginInstallPipeline.installNew`)
32
+
33
+ In order, all-or-nothing:
34
+
35
+ | Stage | What it does | Common failure |
36
+ | --- | --- | --- |
37
+ | **VALIDATING** | schema + block-registry validation; artifact persistence | immutable-artifact conflict; a page block property that doesn't exist |
38
+ | **LOADING** | PF4J loads `lib/*.jar` (or the built-in entry point for a pure-JSON plugin), calls the plugin's `start()` | a declared `mainClass` not found in the jar; **another version of the same plugin id already loaded** |
39
+ | entities | create / `ALTER TABLE ADD COLUMN` (idempotent-additive — never drops) | `label` not valid JSON; `required` without `nullable` |
40
+ | providers / views / services / pages / menus | upsert each artifact | route/module mismatch |
41
+ | rules / workflows | install | `stagesJson` authored as an object not a string |
42
+ | i18n / roles | install | **role `name` or `code` collides with another installed plugin** |
43
+ | seed data | `PluginDataSeedInstaller` upserts each `metadata/seed-data/*.json` by `keyFields` | entity not shipped; non-unique key |
44
+ | register rules fire | `AFTER_CREATE` rules on config entities call `ensure<Job>Registered` → your scheduled jobs get an `erp_job` row | (seed no config row → job never registers) |
45
+
46
+ If any stage throws, **everything rolls back**, including the PF4J load. The
47
+ error names the stage:
48
+
49
+ ```
50
+ plugin install failed at stage LOADING: ...
51
+ plugin install failed after code load, rolled back PF4J load: Role "..." already exists
52
+ ```
53
+
54
+ ## Upgrade
55
+
56
+ Publishing a higher version while the plugin is installed is an **upgrade**. The
57
+ platform's convergence loop unloads the old PF4J code, loads the new, and re-runs
58
+ the artifact/seed stages (all upserts, nothing dropped).
59
+
60
+ ## Uninstall (and why not to, mid-development)
61
+
62
+ `DELETE /api/v1/authoring/plugins/<id>` marks the installation removed. The
63
+ **code** is unloaded asynchronously by a background convergence loop, not
64
+ immediately. If that unload stalls (it can, on a transient DB error), the old
65
+ PF4J code stays loaded, and the next `publish` — which uses the *install-new*
66
+ path — is refused with "already loaded plugin ... same id". Recovery is a backend
67
+ restart or a new plugin id.
68
+
69
+ **Practical rule: to iterate, bump the version and re-publish. Don't uninstall.**
70
+
71
+ ## Verify an install
72
+
73
+ ```bash
74
+ erp plugin list # your plugin: state "installed", pf4jState "STARTED"
75
+ erp api get "/api/v1/authoring/pages?module=<yourModule>" # pages registered
76
+ ```
77
+
78
+ ## What to read next
79
+
80
+ - [Publish and upgrade a plugin](../guides/publish-and-upgrade.md)
81
+ - [Troubleshooting: publishing and install errors](../troubleshooting/publishing-errors.md)
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: How do the SDK modes work (monorepo vs connected)?
3
+ audience: tenant
4
+ ---
5
+
6
+ # How do the SDK modes work (monorepo vs connected)?
7
+
8
+ The `erp` CLI and the `erp-plugin-language` MCP server both need **grounding
9
+ data** — the JSON Schemas, the catalog of platform capabilities, the block
10
+ registry, curated examples, these docs. Where that data comes from depends on how
11
+ the SDK is installed.
12
+
13
+ ## The authoring bundle
14
+
15
+ The grounding data is packaged as an **authoring bundle**: a folder of
16
+ `schemas/`, a frozen `catalog.json`, `blocks.json`, `validators/`, `examples/`,
17
+ and the tenant-public docs. It is produced by `erp bundle build`, which is the
18
+ **only** step that reads platform source (it scans the Java for REST mappings and
19
+ runs esbuild on the validators). Clients never do that scan.
20
+
21
+ ## Mode 1 — monorepo (platform developers)
22
+
23
+ You're working inside the ERP source tree. `erp bundle build` runs locally
24
+ against the live source; `erp` and the MCP server read the freshly built bundle.
25
+ Everything is always current. This is how these docs were written and how the
26
+ tutorial module was verified.
27
+
28
+ ## Mode 2 — connected (you, a third-party developer)
29
+
30
+ You have the SDK as a package, and **no platform source**. Two data sources, in
31
+ priority order:
32
+
33
+ 1. **`erp env sync`** downloads your ERP's own authoring bundle from
34
+ `{baseUrl}/api/v1/dev/bundle` into `~/.erp/cache/<env>/`. This is bundle data
35
+ generated *by your ERP*, so it matches exactly the version you're building
36
+ against — including the live list of entity and theme names in your tenant.
37
+ Re-run it after every ERP upgrade.
38
+ 2. **the bundle shipped inside the SDK package** — a fallback used when your ERP
39
+ doesn't expose `/api/v1/dev/bundle` yet. It's a snapshot; it may lag your
40
+ ERP's actual version.
41
+
42
+ `erp env sync --check` tells you whether your cached bundle has drifted from what
43
+ the server would send.
44
+
45
+ ## What each mode can and can't do offline
46
+
47
+ | Capability | monorepo | connected + synced | connected, offline fallback |
48
+ | --- | --- | --- | --- |
49
+ | pull schemas, list blocks, list examples | yes | yes | yes (may lag) |
50
+ | validate a page / lint semantics | yes | yes | yes |
51
+ | `erp api ...` against the live ERP | yes | yes | no (needs the server) |
52
+ | `erp logs tail` for a failed call's stack trace | yes | yes | no — use the ERP admin log view |
53
+ | know your tenant's real entity / theme names | yes | yes (from the sync) | no |
54
+ | publish a plugin | yes | yes | no |
55
+
56
+ ## No Java toolchain required
57
+
58
+ A pure-JSON plugin (the default shape) needs **no JDK, no pf4j jars, no compiled
59
+ stub**. Leave `plugin.json`'s `mainClass` `null` and the platform loads the
60
+ plugin through a built-in generic entry point. You only reach for a Java
61
+ toolchain if a plugin ships its own `Plugin` subclass for behaviour the engines
62
+ can't express — see [Create a plugin](../guides/create-a-plugin.md), and for a
63
+ complete, real Java-plus-React example, [Build a plugin with custom React +
64
+ Java code](../guides/build-a-code-plugin.md). A custom React component on its
65
+ own, reading data through the generic query engine with no Java at all, is
66
+ [Add a custom block](../guides/add-a-custom-block.md) instead.
67
+
68
+ ## What to read next
69
+
70
+ - [Set up the SDK](../guides/set-up-the-sdk.md)
71
+ - [Use the MCP server with an AI agent](../guides/use-the-mcp-server.md)