@adminiumjs/engine 0.1.0-rc.1

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 (94) hide show
  1. package/LICENSE +661 -0
  2. package/dist/adapter-registry.d.ts +62 -0
  3. package/dist/adapter-registry.d.ts.map +1 -0
  4. package/dist/adapter-registry.js +75 -0
  5. package/dist/adapter-registry.js.map +1 -0
  6. package/dist/adapter.d.ts +384 -0
  7. package/dist/adapter.d.ts.map +1 -0
  8. package/dist/adapter.js +54 -0
  9. package/dist/adapter.js.map +1 -0
  10. package/dist/capabilities.d.ts +57 -0
  11. package/dist/capabilities.d.ts.map +1 -0
  12. package/dist/capabilities.js +131 -0
  13. package/dist/capabilities.js.map +1 -0
  14. package/dist/classify/columns.d.ts +57 -0
  15. package/dist/classify/columns.d.ts.map +1 -0
  16. package/dist/classify/columns.js +535 -0
  17. package/dist/classify/columns.js.map +1 -0
  18. package/dist/classify/index.d.ts +29 -0
  19. package/dist/classify/index.d.ts.map +1 -0
  20. package/dist/classify/index.js +44 -0
  21. package/dist/classify/index.js.map +1 -0
  22. package/dist/classify/names.d.ts +11 -0
  23. package/dist/classify/names.d.ts.map +1 -0
  24. package/dist/classify/names.js +19 -0
  25. package/dist/classify/names.js.map +1 -0
  26. package/dist/classify/pii.d.ts +29 -0
  27. package/dist/classify/pii.d.ts.map +1 -0
  28. package/dist/classify/pii.js +55 -0
  29. package/dist/classify/pii.js.map +1 -0
  30. package/dist/classify/tables.d.ts +64 -0
  31. package/dist/classify/tables.d.ts.map +1 -0
  32. package/dist/classify/tables.js +303 -0
  33. package/dist/classify/tables.js.map +1 -0
  34. package/dist/config-schema/envelope.d.ts +83 -0
  35. package/dist/config-schema/envelope.d.ts.map +1 -0
  36. package/dist/config-schema/envelope.js +87 -0
  37. package/dist/config-schema/envelope.js.map +1 -0
  38. package/dist/config-schema/index.d.ts +14 -0
  39. package/dist/config-schema/index.d.ts.map +1 -0
  40. package/dist/config-schema/index.js +15 -0
  41. package/dist/config-schema/index.js.map +1 -0
  42. package/dist/config-schema/migrations.d.ts +44 -0
  43. package/dist/config-schema/migrations.d.ts.map +1 -0
  44. package/dist/config-schema/migrations.js +62 -0
  45. package/dist/config-schema/migrations.js.map +1 -0
  46. package/dist/generate/archetype.d.ts +76 -0
  47. package/dist/generate/archetype.d.ts.map +1 -0
  48. package/dist/generate/archetype.js +250 -0
  49. package/dist/generate/archetype.js.map +1 -0
  50. package/dist/generate/crud.d.ts +36 -0
  51. package/dist/generate/crud.d.ts.map +1 -0
  52. package/dist/generate/crud.js +45 -0
  53. package/dist/generate/crud.js.map +1 -0
  54. package/dist/generate/dashboard.d.ts +43 -0
  55. package/dist/generate/dashboard.d.ts.map +1 -0
  56. package/dist/generate/dashboard.js +61 -0
  57. package/dist/generate/dashboard.js.map +1 -0
  58. package/dist/generate/domains.d.ts +29 -0
  59. package/dist/generate/domains.d.ts.map +1 -0
  60. package/dist/generate/domains.js +145 -0
  61. package/dist/generate/domains.js.map +1 -0
  62. package/dist/generate/index.d.ts +78 -0
  63. package/dist/generate/index.d.ts.map +1 -0
  64. package/dist/generate/index.js +301 -0
  65. package/dist/generate/index.js.map +1 -0
  66. package/dist/generate/util.d.ts +46 -0
  67. package/dist/generate/util.d.ts.map +1 -0
  68. package/dist/generate/util.js +123 -0
  69. package/dist/generate/util.js.map +1 -0
  70. package/dist/index.d.ts +8 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +8 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/schema-model.d.ts +1853 -0
  75. package/dist/schema-model.d.ts.map +1 -0
  76. package/dist/schema-model.js +463 -0
  77. package/dist/schema-model.js.map +1 -0
  78. package/dist/snapshot/diff.d.ts +258 -0
  79. package/dist/snapshot/diff.d.ts.map +1 -0
  80. package/dist/snapshot/diff.js +151 -0
  81. package/dist/snapshot/diff.js.map +1 -0
  82. package/dist/snapshot/hash.d.ts +23 -0
  83. package/dist/snapshot/hash.d.ts.map +1 -0
  84. package/dist/snapshot/hash.js +47 -0
  85. package/dist/snapshot/hash.js.map +1 -0
  86. package/dist/snapshot/index.d.ts +28 -0
  87. package/dist/snapshot/index.d.ts.map +1 -0
  88. package/dist/snapshot/index.js +26 -0
  89. package/dist/snapshot/index.js.map +1 -0
  90. package/dist/snapshot/sha256.d.ts +14 -0
  91. package/dist/snapshot/sha256.d.ts.map +1 -0
  92. package/dist/snapshot/sha256.js +99 -0
  93. package/dist/snapshot/sha256.js.map +1 -0
  94. package/package.json +37 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * `generatePages()` — Generator v1 (M4-T08, 05-introspection-engine.md
3
+ * generation section, research/widget-registry.md §14–§15, 09-generated-app.md
4
+ * §2.2 nav rules, §8.4 intent variants).
5
+ *
6
+ * Pure and deterministic: same classified model + options in, same
7
+ * `PageEnvelope[]` out (05 §9 — regeneration is safe to re-run). Every
8
+ * emitted envelope is validated against `pageEnvelopeSchema` (and dashboard
9
+ * widget instances against `widgetConfigSchema`) before return — outputs
10
+ * cannot drift from the frozen config contract.
11
+ */
12
+ import { domainHasDashboardSignal, emitCandidates, isRegisteredWidgetId, selectArchetype, } from '@adminium/widgets/generate';
13
+ import { z } from 'zod';
14
+ import { classifyModel } from '../classify/index.js';
15
+ import { pageEnvelopeSchema, widgetConfigSchema, } from '../config-schema/envelope.js';
16
+ import { archetypeSlug, buildArchetypeEnvelope, toCandidateModel } from './archetype.js';
17
+ import { buildCrudEnvelope } from './crud.js';
18
+ import { buildDashboardEnvelope } from './dashboard.js';
19
+ import { detectDomains } from './domains.js';
20
+ import { hashEnvelope, humanize, slugify, SlugRegistry } from './util.js';
21
+ export { archetypeSlug, buildArchetypeEnvelope, composeRequestedArchetype, toCandidateModel, } from './archetype.js';
22
+ export { buildCrudEnvelope, enumTones } from './crud.js';
23
+ export { buildDashboardEnvelope, hasDashboardSignal, } from './dashboard.js';
24
+ export { detectDomains } from './domains.js';
25
+ export { ID_SLUG_BUDGET, MAX_SLUG_LENGTH, hashEnvelope, humanize, pageIdFor, pluralizeWord, slugify, SlugRegistry, } from './util.js';
26
+ /** 09 §8.4 generation intent variants (adminium_connections.settings.intent). */
27
+ export const GENERATE_INTENTS = [
28
+ 'full-admin',
29
+ 'read-only-analytics',
30
+ 'crud',
31
+ 'support-console',
32
+ ];
33
+ export const generateIntentSchema = z.enum(GENERATE_INTENTS);
34
+ /** One `page-dashboard` per major domain, cap 3 for v1. */
35
+ export const DASHBOARD_CAP = 3;
36
+ /**
37
+ * The §14 archetypes each 09 §8.4 intent OMITS (empty ⇒ it admits them all).
38
+ * `crud` is absent because it skips the archetype pass wholesale ("`page-crud`
39
+ * per table … no dashboards beyond a minimal home").
40
+ *
41
+ * An archetype is omitted when the intent's §8.4 row rules out the *surface*, not
42
+ * merely the permission: capping roles at Viewer still leaves a kanban whose
43
+ * whole affordance is dragging a row to a new status, and a page's `toolbar` /
44
+ * `overlays` chrome is generated per template, not per role. Dropping the page is
45
+ * what makes the intent mean what §8.4 says it means.
46
+ */
47
+ const INTENT_OMITTED_ARCHETYPES = {
48
+ 'full-admin': [],
49
+ // §8.4: "dashboards, analytics, data grids (read-only), search, exports; no
50
+ // forms/boards/imports". page-board is the board; page-files is the import
51
+ // surface (upload-dropzone); page-queue-inbox (modal-wizard approvals) and
52
+ // page-chat (message compose) are the form surfaces.
53
+ 'read-only-analytics': ['page-board', 'page-chat', 'page-files', 'page-queue-inbox'],
54
+ // §8.4: "queue/master-detail templates prioritized …; boards/analytics omitted".
55
+ 'support-console': ['page-board'],
56
+ };
57
+ /** Nav icon per table shape (lucide names, 09 §2.2). */
58
+ const SHAPE_ICONS = {
59
+ people: 'users',
60
+ workflow: 'kanban-square',
61
+ events: 'calendar',
62
+ catalog: 'package',
63
+ log: 'scroll-text',
64
+ settings: 'settings',
65
+ geo: 'map',
66
+ join: 'link',
67
+ generic: 'table',
68
+ };
69
+ function splitTables(model, classified, warnings) {
70
+ const included = [];
71
+ const graph = [];
72
+ for (const table of [...model.tables].sort((a, b) => a.id.localeCompare(b.id))) {
73
+ const info = classified.get(table.id);
74
+ const role = info?.semantics.role ?? table.semantics?.role ?? 'entity';
75
+ if (table.system || role === 'system') {
76
+ warnings.push(`skipped system table ${table.id} (05 §8.2)`);
77
+ continue;
78
+ }
79
+ if (role === 'join-table') {
80
+ warnings.push(`skipped join table ${table.id} — hidden from nav, relation still powers M2M (05 §8.2)`);
81
+ graph.push(table);
82
+ continue;
83
+ }
84
+ included.push(table);
85
+ graph.push(table);
86
+ }
87
+ return { included, graph };
88
+ }
89
+ /**
90
+ * The §14 archetype pass: for every included table, the highest-scoring §14
91
+ * trigger (04 §8 H2: "1 page archetype per table … `page-crud` always emitted
92
+ * regardless") composed into a page. Tables that trigger nothing, tie, or whose
93
+ * template has an unfillable `required` slot yield nothing.
94
+ */
95
+ function archetypePages(candidateModel, tables, opts) {
96
+ const { connectionId, warnings } = opts;
97
+ if (connectionId === null) {
98
+ warnings.push('no connection id — §14 archetype pages skipped (query descriptors bind to a connection)');
99
+ return [];
100
+ }
101
+ const ctx = {
102
+ connectionId,
103
+ model: candidateModel,
104
+ isRegistered: opts.isRegistered,
105
+ };
106
+ const byId = new Map(tables.map((table) => [table.id, table]));
107
+ const out = [];
108
+ for (const entry of candidateModel) {
109
+ const table = byId.get(entry.table.id);
110
+ const placement = opts.crudPlacement.get(entry.table.id);
111
+ if (table === undefined || placement === undefined)
112
+ continue;
113
+ const selection = selectArchetype(entry.table, entry.classified, ctx);
114
+ if (selection === null)
115
+ continue;
116
+ if (opts.omittedArchetypes.has(selection.template)) {
117
+ warnings.push(`${selection.template} for ${table.id} skipped — the generation intent omits this archetype (09 §8.4)`);
118
+ continue;
119
+ }
120
+ const candidates = emitCandidates(entry.table, entry.classified, ctx);
121
+ const slug = opts.slugs.claim(archetypeSlug(placement.slug, selection.template));
122
+ const built = buildArchetypeEnvelope(table, selection, candidates, {
123
+ connectionId,
124
+ slug,
125
+ // Sits directly under its table's page-crud in the nav (crud orders step
126
+ // by 10), so the archetype never shifts a crud page's position.
127
+ navOrder: placement.navOrder + 5,
128
+ isRegistered: opts.isRegistered,
129
+ });
130
+ if (built.envelope === null) {
131
+ const detail = built.warnings
132
+ .filter((w) => w.code === 'required-slot-unfillable' || w.code === 'invalid-layout')
133
+ .map((w) => w.message)
134
+ .join('; ');
135
+ warnings.push(`${selection.template} for ${table.id} skipped — ${detail || 'composition produced no page'}`);
136
+ continue;
137
+ }
138
+ const id = built.envelope['id'];
139
+ if (opts.emittedIds.has(id)) {
140
+ // Two slugs colliding after the char(36) id budget (util.ts `pageIdFor`).
141
+ // Dropping the archetype is the safe branch: a crud page must never be
142
+ // overwritten by an upsert on a duplicate id.
143
+ warnings.push(`${selection.template} for ${table.id} skipped — page id "${id}" already taken`);
144
+ continue;
145
+ }
146
+ opts.emittedIds.add(id);
147
+ out.push(built.envelope);
148
+ }
149
+ return out;
150
+ }
151
+ /**
152
+ * Generate the v1 page set for a classified model: one `page-crud` per
153
+ * included table plus one `page-dashboard` per FK-cluster domain (cap
154
+ * {@link DASHBOARD_CAP}), honoring the 09 §8.4 intent. The model should come
155
+ * out of `applyClassification` (snapshots persist that form); classification
156
+ * is recomputed here regardless — `classifyModel` is pure and cheap, and the
157
+ * generator also needs the non-persisted shape/displayColumn outputs. Column
158
+ * stamps with `source: 'llm' | 'override'` still beat the recomputed tags
159
+ * (05 §7, `archetype.ts#toClassifiedInput`); heuristic stamps do not — the
160
+ * recomputed classification of the model as handed in (post `includedTables`
161
+ * filtering) is authoritative.
162
+ */
163
+ export function generatePages(model, opts = {}) {
164
+ const intent = opts.intent ?? 'full-admin';
165
+ const isRegistered = opts.isRegistered ?? isRegisteredWidgetId;
166
+ const warnings = [];
167
+ const connectionId = opts.connectionId !== undefined
168
+ ? opts.connectionId
169
+ : model.source.kind === 'live'
170
+ ? model.source.connectionId
171
+ : null;
172
+ const classified = new Map(classifyModel(model).tables.map((t) => [t.tableId, t]));
173
+ const { included: tables, graph } = splitTables(model, classified, warnings);
174
+ const includedIdSet = new Set(tables.map((t) => t.id));
175
+ const slugs = new SlugRegistry();
176
+ const rawPages = [];
177
+ // The Engine↔Registry adapter runs ONCE — crud bodies, domain dashboards and
178
+ // the archetype pass all read the same mirrored model (archetype.ts is where
179
+ // the two vocabularies meet).
180
+ const candidateModel = toCandidateModel(model, tables, classified);
181
+ const entryById = new Map(candidateModel.map((entry) => [entry.table.id, entry]));
182
+ // -- dashboards first: they own the WORKSPACE group and low nav orders ----
183
+ // `crud` gets "no dashboards beyond a minimal home" and `support-console` has
184
+ // "analytics omitted" — both per 09 §8.4.
185
+ const wantDashboards = intent !== 'crud' && intent !== 'support-console';
186
+ if (wantDashboards) {
187
+ if (connectionId === null) {
188
+ warnings.push('no connection id — dashboards skipped (query descriptors bind to a connection)');
189
+ }
190
+ else {
191
+ // Join tables participate in the graph (they connect M2M sides) but
192
+ // never appear as domain members downstream.
193
+ const domains = detectDomains(model, graph)
194
+ .map((domain) => ({
195
+ ...domain,
196
+ tableIds: domain.tableIds.filter((id) => includedIdSet.has(id)),
197
+ }))
198
+ .filter((domain) => domain.tableIds.length > 0);
199
+ const eligible = domains.filter((domain) => {
200
+ if (domainHasDashboardSignal(domain, candidateModel, model.relations))
201
+ return true;
202
+ warnings.push(`domain ${domain.key} has no timestamp column — dashboard skipped (05 §8)`);
203
+ return false;
204
+ });
205
+ if (eligible.length > DASHBOARD_CAP) {
206
+ warnings.push(`${eligible.length} dashboard-eligible domains — capped at ${DASHBOARD_CAP} for v1`);
207
+ }
208
+ const chosen = eligible.slice(0, DASHBOARD_CAP);
209
+ const multi = chosen.length > 1;
210
+ let navOrder = 10;
211
+ for (const domain of chosen) {
212
+ const slug = slugs.claim(multi ? slugify(`dashboard-${domain.key}`) : 'dashboard');
213
+ const title = multi ? `${domain.label} Dashboard` : 'Dashboard';
214
+ const built = buildDashboardEnvelope(domain, candidateModel, model.relations, {
215
+ connectionId,
216
+ slug,
217
+ title,
218
+ navOrder,
219
+ isRegistered,
220
+ });
221
+ if (built.envelope === null) {
222
+ // Unreachable after eligibility unless a required slot cannot fill
223
+ // (a registry stripped of kpi/line widgets) — the domain keeps no page.
224
+ const detail = built.warnings
225
+ .filter((w) => w.code === 'required-slot-unfillable' || w.code === 'invalid-layout')
226
+ .map((w) => w.message)
227
+ .join('; ');
228
+ warnings.push(`page-dashboard for domain ${domain.key} skipped — ${detail || 'composition produced no page'}`);
229
+ continue;
230
+ }
231
+ rawPages.push(built.envelope);
232
+ navOrder += 1;
233
+ }
234
+ }
235
+ }
236
+ // -- one page-crud per included table (research/widget-registry.md §14) --
237
+ const readOnly = intent === 'read-only-analytics';
238
+ const includedIds = includedIdSet;
239
+ const counters = { library: 0, people: 0 };
240
+ /** Each table's page-crud slug + nav order — the archetype pass sits beside them. */
241
+ const crudPlacement = new Map();
242
+ for (const table of tables) {
243
+ const info = classified.get(table.id);
244
+ const entry = entryById.get(table.id);
245
+ const people = info.shape.kind === 'people' || info.semantics.role === 'people';
246
+ const group = people ? 'people' : 'library';
247
+ const order = 20 + 10 * (people ? counters.people++ : counters.library++);
248
+ const ctx = {
249
+ connectionId,
250
+ slug: slugs.claim(slugify(table.name)),
251
+ navGroup: group,
252
+ navIcon: SHAPE_ICONS[info.shape.kind] ?? 'table',
253
+ navOrder: order,
254
+ readOnly,
255
+ includedTableIds: includedIds,
256
+ relations: model.relations,
257
+ };
258
+ crudPlacement.set(table.id, { slug: ctx.slug, navOrder: order });
259
+ rawPages.push(buildCrudEnvelope(entry, ctx));
260
+ }
261
+ // -- plus the highest-scoring §14 archetype per table (§15 step 3) -------
262
+ //
263
+ // Strictly additive, and strictly LAST: the crud/dashboard envelopes above
264
+ // are the v0.1-gate-proven output, so this pass may not perturb them. Running
265
+ // after the crud loop is what guarantees it — archetype slugs are claimed from
266
+ // the same `SlugRegistry` only once every crud slug already holds its name,
267
+ // and nav orders slot between the crud orders (+5) without touching the
268
+ // counters. `test/generate-baseline.test.ts` is the regression guard.
269
+ if (intent !== 'crud') {
270
+ for (const raw of archetypePages(candidateModel, tables, {
271
+ connectionId,
272
+ slugs,
273
+ crudPlacement,
274
+ warnings,
275
+ isRegistered,
276
+ omittedArchetypes: new Set(INTENT_OMITTED_ARCHETYPES[intent] ?? []),
277
+ emittedIds: new Set(rawPages.map((page) => page['id'])),
278
+ })) {
279
+ rawPages.push(raw);
280
+ }
281
+ }
282
+ // -- stamp generated hashes + validate against the frozen contract -------
283
+ const pages = [];
284
+ for (const raw of rawPages) {
285
+ const config = raw['config'];
286
+ config['generatedHash'] = hashEnvelope(raw);
287
+ const envelope = pageEnvelopeSchema.parse(raw);
288
+ if (envelope.kind === 'dashboard') {
289
+ const layout = envelope.config['layout'];
290
+ for (const item of layout.items)
291
+ widgetConfigSchema.parse(item);
292
+ }
293
+ pages.push(envelope);
294
+ }
295
+ return { pages, warnings };
296
+ }
297
+ /** Human-readable page label — used by server replies and the demo script. */
298
+ export function pageLabel(page) {
299
+ return page.title.fallback || humanize(page.id.replace(/^page_/, ''));
300
+ }
301
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared helpers for the generator (05-introspection-engine.md generation
3
+ * section, 09-generated-app.md §2.2 slug rules).
4
+ *
5
+ * Everything here is pure and deterministic — `generatePages()` promises
6
+ * "same inputs, same output" (05 §9), so no randomness, no Date.now().
7
+ */
8
+ /**
9
+ * Meta `id` columns are char(36) (07-meta-store.md §2.1); generated page ids
10
+ * are `page_<slug>` (5 + slug), so slugs cap at 31 characters.
11
+ */
12
+ export declare const MAX_SLUG_LENGTH = 31;
13
+ /** `Order_Details` / `orderDetails` → `order-details` (kebab-case, 09 §2.2). */
14
+ export declare function slugify(name: string): string;
15
+ /** Deterministic unique-slug allocator: `orders`, `orders-2`, `orders-3`, … */
16
+ export declare class SlugRegistry {
17
+ private readonly taken;
18
+ claim(base: string): string;
19
+ }
20
+ /** `order_details` → `Order Details`; `public.orders` → `Orders`. */
21
+ export declare function humanize(name: string): string;
22
+ /** Tiny pluralizer for domain/table labels ("Order" → "Orders"). */
23
+ export declare function pluralizeWord(word: string): string;
24
+ /**
25
+ * `generated_hash` for a page document (01-architecture.md §6.1 / 04 §6.3):
26
+ * sha256 over the canonical (sorted-keys) JSON of the envelope with the
27
+ * embedded `config.generatedHash` itself excluded. M5 regeneration compares
28
+ * this against the stored document to tell user-edited pages ("user delta
29
+ * wins") from untouched generated ones (updated in place).
30
+ */
31
+ /**
32
+ * How many characters of the slug fit into a connection-scoped page id:
33
+ * 'page_' (5) + hash8 (8) + '_' (1) = 14, leaving 22 of the char(36) PK.
34
+ */
35
+ export declare const ID_SLUG_BUDGET = 22;
36
+ /**
37
+ * Stable, connection-scoped page id. Ids are GLOBALLY unique in
38
+ * adminium_pages (char(36) PK), while slugs are only unique per connection —
39
+ * so multi-connection installs need the connection folded into the id
40
+ * (regression: second connection's 'page_customers' collided with the
41
+ * first's). Connection-less (universal) pages keep the bare readable form
42
+ * ('page_' + a slug already capped at 31 = 36).
43
+ */
44
+ export declare function pageIdFor(connectionId: string | null, slug: string): string;
45
+ export declare function hashEnvelope(envelope: Record<string, unknown>): string;
46
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/generate/util.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,gFAAgF;AAChF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED,+EAA+E;AAC/E,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAS5B;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlD;AAaD;;;;;;GAMG;AACH;;;GAGG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAoBjC;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAOtE"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Shared helpers for the generator (05-introspection-engine.md generation
3
+ * section, 09-generated-app.md §2.2 slug rules).
4
+ *
5
+ * Everything here is pure and deterministic — `generatePages()` promises
6
+ * "same inputs, same output" (05 §9), so no randomness, no Date.now().
7
+ */
8
+ import { sha256Hex } from '../snapshot/sha256.js';
9
+ /**
10
+ * Meta `id` columns are char(36) (07-meta-store.md §2.1); generated page ids
11
+ * are `page_<slug>` (5 + slug), so slugs cap at 31 characters.
12
+ */
13
+ export const MAX_SLUG_LENGTH = 31;
14
+ /** `Order_Details` / `orderDetails` → `order-details` (kebab-case, 09 §2.2). */
15
+ export function slugify(name) {
16
+ const slug = name
17
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
18
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, '-')
21
+ .replace(/^-+|-+$/g, '')
22
+ .replace(/^[^a-z]+/, ''); // slugs must start with a letter (kebab pattern)
23
+ return (slug === '' ? 'page' : slug).slice(0, MAX_SLUG_LENGTH).replace(/-+$/, '');
24
+ }
25
+ /** Deterministic unique-slug allocator: `orders`, `orders-2`, `orders-3`, … */
26
+ export class SlugRegistry {
27
+ taken = new Set();
28
+ claim(base) {
29
+ let candidate = base;
30
+ for (let n = 2; this.taken.has(candidate); n += 1) {
31
+ const suffix = `-${n}`;
32
+ candidate = `${base.slice(0, MAX_SLUG_LENGTH - suffix.length)}${suffix}`;
33
+ }
34
+ this.taken.add(candidate);
35
+ return candidate;
36
+ }
37
+ }
38
+ /** `order_details` → `Order Details`; `public.orders` → `Orders`. */
39
+ export function humanize(name) {
40
+ const bare = name.includes('.') ? (name.split('.').pop() ?? name) : name;
41
+ return bare
42
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
43
+ .replace(/[_-]+/g, ' ')
44
+ .trim()
45
+ .split(/\s+/)
46
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
47
+ .join(' ');
48
+ }
49
+ /** Tiny pluralizer for domain/table labels ("Order" → "Orders"). */
50
+ export function pluralizeWord(word) {
51
+ if (/s$/i.test(word))
52
+ return word; // already plural (or an s-final noun)
53
+ if (/(x|z|ch|sh)$/i.test(word))
54
+ return `${word}es`;
55
+ if (/[^aeiou]y$/i.test(word))
56
+ return `${word.slice(0, -1)}ies`;
57
+ return `${word}s`;
58
+ }
59
+ function sortKeysDeep(value) {
60
+ if (Array.isArray(value))
61
+ return value.map(sortKeysDeep);
62
+ if (value !== null && typeof value === 'object') {
63
+ const record = value;
64
+ const sorted = {};
65
+ for (const key of Object.keys(record).sort())
66
+ sorted[key] = sortKeysDeep(record[key]);
67
+ return sorted;
68
+ }
69
+ return value;
70
+ }
71
+ /**
72
+ * `generated_hash` for a page document (01-architecture.md §6.1 / 04 §6.3):
73
+ * sha256 over the canonical (sorted-keys) JSON of the envelope with the
74
+ * embedded `config.generatedHash` itself excluded. M5 regeneration compares
75
+ * this against the stored document to tell user-edited pages ("user delta
76
+ * wins") from untouched generated ones (updated in place).
77
+ */
78
+ /**
79
+ * How many characters of the slug fit into a connection-scoped page id:
80
+ * 'page_' (5) + hash8 (8) + '_' (1) = 14, leaving 22 of the char(36) PK.
81
+ */
82
+ export const ID_SLUG_BUDGET = 22;
83
+ /**
84
+ * The slug as it appears inside a page id, within {@link ID_SLUG_BUDGET}.
85
+ *
86
+ * Must be INJECTIVE: `SlugRegistry` only de-duplicates against the full
87
+ * {@link MAX_SLUG_LENGTH} slug, so a plain `slice(0, 22)` maps distinct slugs to
88
+ * one id — `order_details_archive_2024` and `..._2025` both became
89
+ * `page_<scope>_order-details-archive-`. `pagesRepo.upsertGenerated` rejects
90
+ * duplicate ids, so that collision failed the WHOLE connection's generation run,
91
+ * not just the colliding pair. Over-budget slugs therefore keep a readable head
92
+ * and earn a digest of the full slug, which distinct slugs cannot share.
93
+ */
94
+ function idSlug(slug) {
95
+ if (slug.length <= ID_SLUG_BUDGET)
96
+ return slug;
97
+ const digest = sha256Hex(slug).slice(0, 8);
98
+ const head = slug.slice(0, ID_SLUG_BUDGET - digest.length - 1).replace(/-+$/, '');
99
+ return `${head}-${digest}`;
100
+ }
101
+ /**
102
+ * Stable, connection-scoped page id. Ids are GLOBALLY unique in
103
+ * adminium_pages (char(36) PK), while slugs are only unique per connection —
104
+ * so multi-connection installs need the connection folded into the id
105
+ * (regression: second connection's 'page_customers' collided with the
106
+ * first's). Connection-less (universal) pages keep the bare readable form
107
+ * ('page_' + a slug already capped at 31 = 36).
108
+ */
109
+ export function pageIdFor(connectionId, slug) {
110
+ if (connectionId === null)
111
+ return `page_${slug}`;
112
+ const scope = sha256Hex(connectionId).slice(0, 8);
113
+ return `page_${scope}_${idSlug(slug)}`;
114
+ }
115
+ export function hashEnvelope(envelope) {
116
+ const plain = JSON.parse(JSON.stringify(envelope));
117
+ const config = plain['config'];
118
+ if (config !== null && typeof config === 'object') {
119
+ delete config['generatedHash'];
120
+ }
121
+ return sha256Hex(JSON.stringify(sortKeysDeep(plain)));
122
+ }
123
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/generate/util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /** @adminium/engine — SchemaModel, Adapter interface, classification, config generation. See workplan/05-introspection-engine.md. */
2
+ export declare const PACKAGE_NAME = "@adminium/engine";
3
+ export * from './schema-model.js';
4
+ export * from './adapter.js';
5
+ export * from './classify/index.js';
6
+ export * from './snapshot/index.js';
7
+ export * from './generate/index.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qIAAqI;AACrI,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /** @adminium/engine — SchemaModel, Adapter interface, classification, config generation. See workplan/05-introspection-engine.md. */
2
+ export const PACKAGE_NAME = '@adminium/engine';
3
+ export * from './schema-model.js';
4
+ export * from './adapter.js';
5
+ export * from './classify/index.js';
6
+ export * from './snapshot/index.js';
7
+ export * from './generate/index.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qIAAqI;AACrI,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,CAAC;AAE/C,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}