@milehimikey/em 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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +160 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/emit/dot.d.ts +3 -0
  7. package/dist/emit/dot.js +150 -0
  8. package/dist/emit/dot.js.map +1 -0
  9. package/dist/emit/theme.d.ts +9 -0
  10. package/dist/emit/theme.js +35 -0
  11. package/dist/emit/theme.js.map +1 -0
  12. package/dist/layout/grid.d.ts +33 -0
  13. package/dist/layout/grid.js +101 -0
  14. package/dist/layout/grid.js.map +1 -0
  15. package/dist/model/edges.d.ts +8 -0
  16. package/dist/model/edges.js +78 -0
  17. package/dist/model/edges.js.map +1 -0
  18. package/dist/model/model.d.ts +49 -0
  19. package/dist/model/model.js +109 -0
  20. package/dist/model/model.js.map +1 -0
  21. package/dist/model/validate.d.ts +13 -0
  22. package/dist/model/validate.js +127 -0
  23. package/dist/model/validate.js.map +1 -0
  24. package/dist/parser/ast.d.ts +44 -0
  25. package/dist/parser/ast.js +10 -0
  26. package/dist/parser/ast.js.map +1 -0
  27. package/dist/parser/lexer.d.ts +6 -0
  28. package/dist/parser/lexer.js +46 -0
  29. package/dist/parser/lexer.js.map +1 -0
  30. package/dist/parser/parser.d.ts +6 -0
  31. package/dist/parser/parser.js +207 -0
  32. package/dist/parser/parser.js.map +1 -0
  33. package/dist/pipeline.d.ts +11 -0
  34. package/dist/pipeline.js +16 -0
  35. package/dist/pipeline.js.map +1 -0
  36. package/dist/render/drawEdges.d.ts +7 -0
  37. package/dist/render/drawEdges.js +83 -0
  38. package/dist/render/drawEdges.js.map +1 -0
  39. package/dist/render/drawNotes.d.ts +13 -0
  40. package/dist/render/drawNotes.js +117 -0
  41. package/dist/render/drawNotes.js.map +1 -0
  42. package/dist/render/render.d.ts +9 -0
  43. package/dist/render/render.js +106 -0
  44. package/dist/render/render.js.map +1 -0
  45. package/dist/render/svgGeometry.d.ts +10 -0
  46. package/dist/render/svgGeometry.js +47 -0
  47. package/dist/render/svgGeometry.js.map +1 -0
  48. package/dist/render/watch.d.ts +2 -0
  49. package/dist/render/watch.js +18 -0
  50. package/dist/render/watch.js.map +1 -0
  51. package/dist/templates.d.ts +1 -0
  52. package/dist/templates.js +53 -0
  53. package/dist/templates.js.map +1 -0
  54. package/package.json +61 -0
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Reads box rectangles back out of a Graphviz-rendered SVG.
3
+ //
4
+ // Each node is emitted as `<g class="node"><title>ID</title> … <polygon|path …>`
5
+ // with coordinates in the same 1:1 user space we later inject our edges into, so
6
+ // the numbers can be used directly. We take the bounding box of the first shape
7
+ // in each node group.
8
+ const NODE_GROUP = /<g\b[^>]*class="node"[^>]*>([\s\S]*?)<\/g>/g;
9
+ const TITLE = /<title>([\s\S]*?)<\/title>/;
10
+ const SHAPE = /<(?:polygon|path)\b[^>]*\b(?:points|d)="([^"]*)"/;
11
+ const NUM = /-?\d*\.?\d+(?:e-?\d+)?/gi;
12
+ /** Map each requested node id to its rectangle in SVG coordinates. */
13
+ export function parseNodeRects(svg, ids) {
14
+ const rects = new Map();
15
+ for (const m of svg.matchAll(NODE_GROUP)) {
16
+ const body = m[1];
17
+ const id = decode(TITLE.exec(body)?.[1] ?? "");
18
+ if (!ids.has(id))
19
+ continue;
20
+ const shape = SHAPE.exec(body);
21
+ if (!shape)
22
+ continue;
23
+ const nums = (shape[1].match(NUM) ?? []).map(Number);
24
+ const xs = [];
25
+ const ys = [];
26
+ for (let i = 0; i + 1 < nums.length; i += 2) {
27
+ xs.push(nums[i]);
28
+ ys.push(nums[i + 1]);
29
+ }
30
+ if (xs.length === 0)
31
+ continue;
32
+ const left = Math.min(...xs);
33
+ const right = Math.max(...xs);
34
+ const top = Math.min(...ys);
35
+ const bottom = Math.max(...ys);
36
+ rects.set(id, { left, right, top, bottom, cx: (left + right) / 2, cy: (top + bottom) / 2 });
37
+ }
38
+ return rects;
39
+ }
40
+ function decode(s) {
41
+ return s
42
+ .replace(/&lt;/g, "<")
43
+ .replace(/&gt;/g, ">")
44
+ .replace(/&amp;/g, "&")
45
+ .replace(/&#45;/g, "-");
46
+ }
47
+ //# sourceMappingURL=svgGeometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svgGeometry.js","sourceRoot":"","sources":["../../src/render/svgGeometry.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,4DAA4D;AAC5D,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,sBAAsB;AAWtB,MAAM,UAAU,GAAG,6CAA6C,CAAC;AACjE,MAAM,KAAK,GAAG,4BAA4B,CAAC;AAC3C,MAAM,KAAK,GAAG,kDAAkD,CAAC;AACjE,MAAM,GAAG,GAAG,0BAA0B,CAAC;AAEvC,sEAAsE;AACtE,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,GAAgB;IAC1D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,SAAS;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,EAAE,GAAa,EAAE,CAAC;QACxB,MAAM,EAAE,GAAa,EAAE,CAAC;QACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACjB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;QACD,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/B,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC;SACL,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,2 @@
1
+ import chokidar from "chokidar";
2
+ export declare function watchFile(file: string, onChange: () => void | Promise<void>, debounceMs?: number): chokidar.FSWatcher;
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Debounced file watcher for `em watch`.
3
+ import chokidar from "chokidar";
4
+ export function watchFile(file, onChange, debounceMs = 80) {
5
+ const watcher = chokidar.watch(file, { ignoreInitial: true });
6
+ let timer = null;
7
+ const trigger = () => {
8
+ if (timer)
9
+ clearTimeout(timer);
10
+ timer = setTimeout(() => {
11
+ void onChange();
12
+ }, debounceMs);
13
+ };
14
+ watcher.on("change", trigger);
15
+ watcher.on("add", trigger);
16
+ return watcher;
17
+ }
18
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/render/watch.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,yCAAyC;AAEzC,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,QAAoC,EACpC,UAAU,GAAG,EAAE;IAEf,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,IAAI,KAAK,GAA0B,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,QAAQ,EAAE,CAAC;QAClB,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const STARTER_EM = "model \"Order Fulfillment\"\n\npersona Customer\npersona Manager\n\ncontext Order\ncontext Payment\n\n# --- Command pattern: UI -> command -> event ---\nslice \"Browse Catalog\" {\n ui Product Catalog @Customer\n command Place Order\n event Order Placed @Order\n}\n\n# --- View pattern: event -> read model -> UI ---\nslice \"View Open Orders\" {\n view Open Orders from \"Order Placed\"\n ui Order List @Customer\n}\n\nslice \"Checkout\" {\n ui Checkout Screen @Customer\n command Submit Payment\n event Payment Requested @Payment\n}\n\n# --- View pattern: a human view of pending payments ---\nslice \"Manager Review\" {\n view Pending Payments from \"Payment Requested\"\n ui Payment Dashboard @Manager\n}\n\n# --- Automation slice: only the read model it reads + the processor ---\nslice \"Payments To Process\" {\n view Payments To Process from \"Payment Requested\"\n processor Payment Gateway\n}\n\n# --- Command slice: the command the automation triggers + its event ---\nslice \"Capture Payment\" {\n command Capture Payment\n event Payment Captured @Payment\n}\n\nslice \"Show Receipt\" {\n view Receipts from \"Payment Captured\"\n ui Receipt Screen @Customer\n}\n";
@@ -0,0 +1,53 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Starter model used by `em init`.
3
+ export const STARTER_EM = `model "Order Fulfillment"
4
+
5
+ persona Customer
6
+ persona Manager
7
+
8
+ context Order
9
+ context Payment
10
+
11
+ # --- Command pattern: UI -> command -> event ---
12
+ slice "Browse Catalog" {
13
+ ui Product Catalog @Customer
14
+ command Place Order
15
+ event Order Placed @Order
16
+ }
17
+
18
+ # --- View pattern: event -> read model -> UI ---
19
+ slice "View Open Orders" {
20
+ view Open Orders from "Order Placed"
21
+ ui Order List @Customer
22
+ }
23
+
24
+ slice "Checkout" {
25
+ ui Checkout Screen @Customer
26
+ command Submit Payment
27
+ event Payment Requested @Payment
28
+ }
29
+
30
+ # --- View pattern: a human view of pending payments ---
31
+ slice "Manager Review" {
32
+ view Pending Payments from "Payment Requested"
33
+ ui Payment Dashboard @Manager
34
+ }
35
+
36
+ # --- Automation slice: only the read model it reads + the processor ---
37
+ slice "Payments To Process" {
38
+ view Payments To Process from "Payment Requested"
39
+ processor Payment Gateway
40
+ }
41
+
42
+ # --- Command slice: the command the automation triggers + its event ---
43
+ slice "Capture Payment" {
44
+ command Capture Payment
45
+ event Payment Captured @Payment
46
+ }
47
+
48
+ slice "Show Receipt" {
49
+ view Receipts from "Payment Captured"
50
+ ui Receipt Screen @Customer
51
+ }
52
+ `;
53
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,mCAAmC;AAEnC,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDzB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@milehimikey/em",
3
+ "version": "0.1.0",
4
+ "description": "AI-friendly Event Modeling — a slice-first text DSL rendered to a strict, self-contained Graphviz grid (SVG/PNG)",
5
+ "type": "module",
6
+ "bin": {
7
+ "em": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/cli.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "dev": "tsx src/cli.ts",
18
+ "em": "tsx src/cli.ts",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "prepublishOnly": "npm run build && npm test"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "license": "MIT",
27
+ "author": "milehimikey",
28
+ "homepage": "https://github.com/milehimikey/em#readme",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/milehimikey/em.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/milehimikey/em/issues"
35
+ },
36
+ "keywords": [
37
+ "event-modeling",
38
+ "event-sourcing",
39
+ "diagram",
40
+ "dsl",
41
+ "graphviz",
42
+ "svg",
43
+ "cli",
44
+ "architecture"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "dependencies": {
50
+ "@hpcc-js/wasm-graphviz": "^1.22.0",
51
+ "@resvg/resvg-js": "^2.6.2",
52
+ "chokidar": "^3.6.0",
53
+ "commander": "^12.1.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.0.0",
57
+ "tsx": "^4.19.0",
58
+ "typescript": "^5.6.0",
59
+ "vitest": "^2.1.0"
60
+ }
61
+ }