create-githolon 0.101.6 → 0.102.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.
- package/index.mjs +9 -6
- package/package.json +1 -1
- package/template/CLAUDE.md +14 -7
- package/template/README.md +51 -35
- package/template/domains/estate.ts +64 -0
- package/template/nomos.application.mjs +15 -0
- package/template/nomos.package.mjs +12 -2
- package/template/package.json +4 -4
- package/template/tsconfig.json +1 -1
- package/template/domains/todo.ts +0 -18
package/index.mjs
CHANGED
|
@@ -17,7 +17,7 @@ var NOMOS_VERSION = (() => {
|
|
|
17
17
|
var args = process.argv.slice(2);
|
|
18
18
|
if (args.includes("-h") || args.includes("--help")) {
|
|
19
19
|
process.stdout.write(
|
|
20
|
-
"create-githolon \u2014 scaffold a Nomos business-law package\n\nUsage:\n npm create githolon <dir> (or: npx create-githolon <dir>)\n\nScaffolds one
|
|
20
|
+
"create-githolon \u2014 scaffold a Nomos business-law package\n\nUsage:\n npm create githolon <dir> (or: npx create-githolon <dir>)\n\nScaffolds one durable business root, one atomic multi-aggregate intent,\none generated application, and a one-page explanation into <dir>\n(default: my-holon-app). The generated\nproof exercises the real local engine.\n\nFlags:\n --no-git skip git init\n"
|
|
21
21
|
);
|
|
22
22
|
process.exit(0);
|
|
23
23
|
}
|
|
@@ -40,8 +40,11 @@ if (existsSync(path.join(targetDir, "vscode"))) {
|
|
|
40
40
|
renameSync(path.join(targetDir, "vscode"), path.join(targetDir, ".vscode"));
|
|
41
41
|
}
|
|
42
42
|
var appHash = `${appName.replace(/[-\s]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}_DOMAIN_HASH`;
|
|
43
|
-
var
|
|
44
|
-
|
|
43
|
+
var appDartName = appName.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "nomos_app";
|
|
44
|
+
var appClassNameRaw = appName.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("") || "NomosApp";
|
|
45
|
+
var appClassName = /^[A-Za-z_]/.test(appClassNameRaw) ? appClassNameRaw : `App${appClassNameRaw}`;
|
|
46
|
+
var subst = (s) => s.replaceAll("__APP_NAME__", appName).replaceAll("__APP_DART_NAME__", appDartName).replaceAll("__APP_CLASS_NAME__", appClassName).replaceAll("__APP_HASH__", appHash).replaceAll("__NOMOS_VERSION__", NOMOS_VERSION);
|
|
47
|
+
for (const f of ["package.json", "README.md", "nomos.package.mjs", "nomos.application.mjs", "CLAUDE.md", "domains/estate.ts"]) {
|
|
45
48
|
const p = path.join(targetDir, f);
|
|
46
49
|
if (existsSync(p)) writeFileSync(p, subst(readFileSync(p, "utf8")), "utf8");
|
|
47
50
|
}
|
|
@@ -64,10 +67,10 @@ Nomos in one page: ${path.join(rel, "README.md")}
|
|
|
64
67
|
Next:
|
|
65
68
|
cd ${rel}
|
|
66
69
|
npm install
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
npm run compile # law + generated business application + proof
|
|
71
|
+
npm run proof # drives the business language in the real local engine
|
|
69
72
|
|
|
70
|
-
The starter exposes one
|
|
73
|
+
The starter exposes one intent: commissionAsset(businessFacts).
|
|
71
74
|
Identity, authorship, persistence, synchronisation, provenance, and recovery stay underneath it.
|
|
72
75
|
`
|
|
73
76
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githolon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.102.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Scaffold a Nomos domain package: the starter domain + compile config + live e2e. `npm create githolon my-app`.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
package/template/CLAUDE.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
# __APP_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Model the business language. Do not recreate Nomos as application infrastructure.
|
|
4
|
+
|
|
5
|
+
- Aggregates are continuing business identities, not database rows or immutable snapshots.
|
|
6
|
+
- `birth(...)` gives one root aggregate a durable life.
|
|
7
|
+
- Public application writes are `intent(...)` declarations collected by `language(...)`.
|
|
8
|
+
- Directives are private consequences composed beneath an intent; never expose them as an application CRUD API.
|
|
9
|
+
- One intent may atomically change any number of aggregates.
|
|
10
|
+
- Relationships use `t.ref(Aggregate)` and `idOf(Aggregate)`; generated code carries `NomosRef<Aggregate>`.
|
|
11
|
+
- Pass the generated application through the UI as the sole business state. Do not add repositories, SQLite
|
|
12
|
+
mirrors, runtime hosts, session providers, custom sync, ID stores, or replacement aggregate versions.
|
|
4
13
|
|
|
5
14
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
npx githolon proof
|
|
15
|
+
npm run compile
|
|
16
|
+
npm run proof
|
|
9
17
|
```
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Do not introduce application-facing identity, signing, storage, deployment, or recovery ceremonies. They belong beneath the generated `birth(payload)` surface.
|
|
19
|
+
If either command exposes framework implementation machinery, treat that as a Nomos defect rather than
|
|
20
|
+
teaching the application to handle it.
|
package/template/README.md
CHANGED
|
@@ -1,59 +1,75 @@
|
|
|
1
1
|
# __APP_NAME__
|
|
2
2
|
|
|
3
|
-
Nomos lets you describe
|
|
3
|
+
Nomos lets you describe what the business means and leaves the compute science to the framework.
|
|
4
4
|
|
|
5
|
-
You write
|
|
5
|
+
You write four things:
|
|
6
6
|
|
|
7
|
-
- aggregates
|
|
8
|
-
- birth
|
|
9
|
-
- directives
|
|
7
|
+
- **aggregates** — business things with one continuing identity;
|
|
8
|
+
- **birth** — the facts required to give a root aggregate its own durable life;
|
|
9
|
+
- **private directives** — the consequences Nomos may apply;
|
|
10
|
+
- **public intents** — the business verbs people and applications actually use.
|
|
10
11
|
|
|
11
|
-
The starter
|
|
12
|
+
The starter's public verb is `commissionAsset`. It creates an asset and claims its RFID/QR identity in one
|
|
13
|
+
atomic accepted change:
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
|
-
export const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
export const commissionAsset = intent("commissionAsset")
|
|
17
|
+
.payload(z.object({
|
|
18
|
+
name: z.string().min(1),
|
|
19
|
+
rfidQrIdentity: z.string().min(1),
|
|
20
|
+
}))
|
|
21
|
+
.directs(createCommissionedAsset, ({ name }) => ({ name }))
|
|
22
|
+
.directs(claimRfidQrIdentity, ({ rfidQrIdentity }) => ({ value: rfidQrIdentity }))
|
|
23
|
+
.atomically();
|
|
24
|
+
|
|
25
|
+
export const estateLanguage = language("estate", [commissionAsset]);
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
The generated
|
|
28
|
+
The generated Flutter application exposes that same language—not its directives:
|
|
25
29
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
reference:
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
```dart
|
|
31
|
+
final estate = await application.estates.birth(
|
|
32
|
+
const CreateEstateInput(reference: 'E-001', name: 'Foundry'),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
final NomosRef<Asset> asset = await estate.commissionAsset(
|
|
36
|
+
const CommissionAssetInput(
|
|
37
|
+
name: 'Main electricity meter',
|
|
38
|
+
rfidQrIdentity: 'E20034120123456789012345',
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
estate.assets.watchOne(asset).listen(render);
|
|
31
43
|
```
|
|
32
44
|
|
|
33
|
-
That is the
|
|
45
|
+
That is the product surface. `EstateApplication` is the application state. Pass it through the UI; do not
|
|
46
|
+
mirror it in repositories, providers, sessions, caches, SQLite tables, or hand-written sync code.
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
`NomosRef<Asset>` is the asset's one identity everywhere. The aggregate is not a database row and an update
|
|
49
|
+
does not create a replacement aggregate: its accepted history changes while its reference stays the same.
|
|
50
|
+
|
|
51
|
+
Nomos owns everything underneath the intent: identity, authorship, validation, atomicity, persistence,
|
|
52
|
+
synchronisation, conflict retention, provenance, access, schema evolution, recovery, and replay.
|
|
36
53
|
|
|
37
54
|
The development loop is one path:
|
|
38
55
|
|
|
39
56
|
```bash
|
|
40
57
|
npm install
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
npm run compile
|
|
59
|
+
npm run proof
|
|
43
60
|
```
|
|
44
61
|
|
|
45
|
-
`compile` emits the law
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
begin that durable life; its field rule is also the birth validation and generated input type. Nomos derives
|
|
49
|
-
the initial write. Keep the rule: application code supplies business facts; Nomos owns the machinery that
|
|
50
|
-
makes those facts durable and trustworthy.
|
|
62
|
+
`compile` emits the law and generated business application. `proof` drives the same business language in the
|
|
63
|
+
real local engine, discards resident state, restores it from durable custody, verifies its history, and reads
|
|
64
|
+
the business world back.
|
|
51
65
|
|
|
52
66
|
Useful files:
|
|
53
67
|
|
|
54
|
-
- [`domains/
|
|
55
|
-
- [`nomos.package.mjs`](nomos.package.mjs):
|
|
56
|
-
- `
|
|
57
|
-
- `build/
|
|
68
|
+
- [`domains/estate.ts`](domains/estate.ts): nouns, private consequences, and public language;
|
|
69
|
+
- [`nomos.package.mjs`](nomos.package.mjs): business-law authoring input; it emits a published USD package asset;
|
|
70
|
+
- [`nomos.application.mjs`](nomos.application.mjs): application-composition authoring input; only the emitted `.application.usda` travels;
|
|
71
|
+
- `build/application/__APP_DART_NAME___application/`: the sole Flutter application dependency;
|
|
72
|
+
- `build/__APP_NAME__.proof.mts`: the generated local proof.
|
|
58
73
|
|
|
59
|
-
If
|
|
74
|
+
If application code needs to understand workspaces, event sourcing, JWTs, signatures, retries, topology,
|
|
75
|
+
serialization, or merge algorithms, a framework concern has leaked out of Nomos.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* __APP_NAME__ — business nouns, one durable life, and one public business verb.
|
|
3
|
+
*
|
|
4
|
+
* The directives below describe private consequences. Tenant applications receive only
|
|
5
|
+
* `commissionAsset`: one intention which changes the business world atomically.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
aggregate,
|
|
9
|
+
birth,
|
|
10
|
+
create,
|
|
11
|
+
directive,
|
|
12
|
+
intent,
|
|
13
|
+
language,
|
|
14
|
+
set,
|
|
15
|
+
t,
|
|
16
|
+
z,
|
|
17
|
+
} from "@githolon/dsl";
|
|
18
|
+
|
|
19
|
+
export const Estate = aggregate("Estate", {
|
|
20
|
+
reference: t.string({ nonempty: true }).fromBirth(),
|
|
21
|
+
name: t.string({ nonempty: true }).fromBirth(),
|
|
22
|
+
}).owned();
|
|
23
|
+
|
|
24
|
+
export const estate = birth(Estate, {
|
|
25
|
+
key: "reference",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const Asset = aggregate("Asset", {
|
|
29
|
+
name: t.string({ nonempty: true }),
|
|
30
|
+
}).owned();
|
|
31
|
+
|
|
32
|
+
export const RfidQrIdentityClaim = aggregate("RfidQrIdentityClaim", {
|
|
33
|
+
value: t.string({ nonempty: true }),
|
|
34
|
+
}).public();
|
|
35
|
+
|
|
36
|
+
/** Private consequence: create the commissioned asset. */
|
|
37
|
+
export const createCommissionedAsset = directive("createCommissionedAsset")
|
|
38
|
+
.creates(Asset)
|
|
39
|
+
.payload(z.object({ name: z.string().min(1) }))
|
|
40
|
+
.plan((facts) => {
|
|
41
|
+
create(Asset).set("name", facts.name);
|
|
42
|
+
return [];
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/** Private consequence: reserve the physical identifier in the same accepted change. */
|
|
46
|
+
export const claimRfidQrIdentity = directive("claimRfidQrIdentity")
|
|
47
|
+
.ensures(RfidQrIdentityClaim)
|
|
48
|
+
.payload(z.object({ value: z.string().min(1) }))
|
|
49
|
+
.plan((facts) => [set(RfidQrIdentityClaim, "value", facts.value)]);
|
|
50
|
+
|
|
51
|
+
/** The application-facing verb: business facts in, one atomic change to the world. */
|
|
52
|
+
export const commissionAsset = intent("commissionAsset")
|
|
53
|
+
.contract({
|
|
54
|
+
accepts: z.object({
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
rfidQrIdentity: z.string().min(1),
|
|
57
|
+
}),
|
|
58
|
+
})
|
|
59
|
+
.directs(createCommissionedAsset, ({ name }) => ({ name }))
|
|
60
|
+
.directs(claimRfidQrIdentity, ({ rfidQrIdentity }) => ({ value: rfidQrIdentity }))
|
|
61
|
+
.atomically();
|
|
62
|
+
|
|
63
|
+
/** This is the complete public language. The two directives above remain implementation. */
|
|
64
|
+
export const estateLanguage = language("estate", [commissionAsset]);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
application: "__APP_NAME__",
|
|
3
|
+
root: {
|
|
4
|
+
name: "__APP_CLASS_NAME__",
|
|
5
|
+
kind: "assembly",
|
|
6
|
+
children: [{
|
|
7
|
+
name: "Business",
|
|
8
|
+
kind: "component",
|
|
9
|
+
reference: {
|
|
10
|
+
asset: "./build/__APP_NAME__.asset.usda",
|
|
11
|
+
prim: "/__APP_CLASS_NAME__Business",
|
|
12
|
+
},
|
|
13
|
+
}],
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
// The package is just the business
|
|
1
|
+
// The package is just the business language Nomos should compile.
|
|
2
2
|
export default {
|
|
3
3
|
name: "__APP_NAME__",
|
|
4
|
-
|
|
4
|
+
rootPart: { name: "__APP_CLASS_NAME__Business", kind: "component" },
|
|
5
|
+
domains: [{ key: "estate", modules: ["./domains/estate.ts"] }],
|
|
6
|
+
dart: {
|
|
7
|
+
out: "./build/application",
|
|
8
|
+
support: "package",
|
|
9
|
+
packages: {
|
|
10
|
+
types: "__APP_DART_NAME___business",
|
|
11
|
+
client: "__APP_DART_NAME___application",
|
|
12
|
+
test: "__APP_DART_NAME___scenarios",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
5
15
|
};
|
package/template/package.json
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "A Nomos business
|
|
6
|
+
"description": "A Nomos business application: public intents over private atomic consequences.",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"compile": "githolon compile",
|
|
8
|
+
"compile": "githolon compile ./nomos.package.mjs && githolon compile ./nomos.application.mjs",
|
|
9
9
|
"proof": "githolon proof",
|
|
10
10
|
"typecheck": "tsc --noEmit"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@githolon/dsl": "^__NOMOS_VERSION__"
|
|
14
|
-
"@githolon/client": "^__NOMOS_VERSION__"
|
|
13
|
+
"@githolon/dsl": "^__NOMOS_VERSION__"
|
|
15
14
|
},
|
|
16
15
|
"devDependencies": {
|
|
17
16
|
"githolon": "^__NOMOS_VERSION__",
|
|
17
|
+
"@githolon/testing": "^__NOMOS_VERSION__",
|
|
18
18
|
"@types/node": "^25.9.2",
|
|
19
19
|
"tsx": "^4.19.2",
|
|
20
20
|
"typescript": "^5.6.3"
|
package/template/tsconfig.json
CHANGED
package/template/domains/todo.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* __APP_NAME__ — the complete starter model.
|
|
3
|
-
*
|
|
4
|
-
* The app supplies business facts. Nomos gives the aggregate a durable life and owns identity,
|
|
5
|
-
* authorship, local execution, synchronisation, persistence, provenance, and recovery beneath it.
|
|
6
|
-
*/
|
|
7
|
-
import { aggregate, birth, t } from "@githolon/dsl";
|
|
8
|
-
|
|
9
|
-
export const TodoList = aggregate("TodoList", {
|
|
10
|
-
/** A stable business key the caller already knows. */
|
|
11
|
-
reference: t.string({ nonempty: true }).fromBirth(),
|
|
12
|
-
title: t.string({ nonempty: true }).fromBirth(),
|
|
13
|
-
}).owned();
|
|
14
|
-
|
|
15
|
-
/** The one public creation idea: give a todo list its own durable life. */
|
|
16
|
-
export const todoList = birth(TodoList, {
|
|
17
|
-
key: "reference",
|
|
18
|
-
});
|