@intent-framework/core 0.1.0-alpha.0 → 0.1.0-alpha.2
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/README.md +67 -0
- package/dist/graph.d.ts +7 -0
- package/dist/index.js +77 -6
- package/dist/registry.d.ts +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @intent-framework/core
|
|
2
|
+
|
|
3
|
+
Platformless semantic graph and runtime for Intent applications.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @intent-framework/core@0.1.0-alpha.1
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @intent-framework/core@0.1.0-alpha.1
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it provides
|
|
16
|
+
|
|
17
|
+
- `screen()` — define a semantic interaction space
|
|
18
|
+
- `$.state.text()` / `$.state.boolean()` / `$.state.choice()` — reactive state
|
|
19
|
+
- `$.ask()` — user-facing question with validation
|
|
20
|
+
- `$.act()` — executable action with conditions, lifecycle, and feedback
|
|
21
|
+
- `$.resource()` — async state with load/reload lifecycle
|
|
22
|
+
- `$.surface()` — named containment surface
|
|
23
|
+
- `createScreenRuntime()` — runtime that owns screen state and resources
|
|
24
|
+
- `inspectScreen()` — semantic graph snapshot with diagnostics
|
|
25
|
+
- Condition and signal primitives
|
|
26
|
+
|
|
27
|
+
## Minimal example
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { screen, inspectScreen } from "@intent-framework/core"
|
|
31
|
+
|
|
32
|
+
const InviteMember = screen("InviteMember", $ => {
|
|
33
|
+
const email = $.state.text("email")
|
|
34
|
+
|
|
35
|
+
const emailAsk = $.ask("Email", email)
|
|
36
|
+
.required("Email is required")
|
|
37
|
+
.validate(value => value.includes("@") ? true : "Enter a valid email")
|
|
38
|
+
|
|
39
|
+
const invite = $.act("Invite member")
|
|
40
|
+
.primary()
|
|
41
|
+
.when(emailAsk.valid, "Enter a valid email first")
|
|
42
|
+
.does(() => {
|
|
43
|
+
console.log("invite", email.value)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
$.surface("main").contains(emailAsk, invite)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const graph = inspectScreen(InviteMember)
|
|
50
|
+
console.log(graph.diagnostics)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Where this fits
|
|
54
|
+
|
|
55
|
+
Core defines the product graph. It has no DOM, React, Node, or framework dependencies. Renderers (`@intent-framework/dom`), the router (`@intent-framework/router`), and testing (`@intent-framework/testing`) all build on core.
|
|
56
|
+
|
|
57
|
+
## Learn more
|
|
58
|
+
|
|
59
|
+
- [Root README](../../README.md) — project overview and philosophy
|
|
60
|
+
- [Quickstart](../../docs/Quickstart.md) — step-by-step guide
|
|
61
|
+
- [Inspect Screen and Diagnostics Guide](../../docs/Inspect-Screen.md) — graph inspection and diagnostics
|
|
62
|
+
- [Resources Guide](../../docs/Resources.md) — resource lifecycle and runtime scoping
|
|
63
|
+
- [MVP Checkpoint](../../docs/MVP-Checkpoint.md) — current implementation boundaries
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
Experimental alpha. Version `0.1.0-alpha.1`. APIs may change. Not recommended for production use.
|
package/dist/graph.d.ts
CHANGED
|
@@ -7,11 +7,14 @@ export type GraphDiagnostic = {
|
|
|
7
7
|
code: string;
|
|
8
8
|
message: string;
|
|
9
9
|
nodeId?: string;
|
|
10
|
+
semanticNodeId?: string;
|
|
10
11
|
};
|
|
11
12
|
export type InspectedScreen = {
|
|
12
13
|
name: string;
|
|
14
|
+
semanticId: string;
|
|
13
15
|
asks: Array<{
|
|
14
16
|
id: string;
|
|
17
|
+
semanticId: string;
|
|
15
18
|
label: string;
|
|
16
19
|
kind: string;
|
|
17
20
|
required: boolean;
|
|
@@ -21,6 +24,7 @@ export type InspectedScreen = {
|
|
|
21
24
|
}>;
|
|
22
25
|
acts: Array<{
|
|
23
26
|
id: string;
|
|
27
|
+
semanticId: string;
|
|
24
28
|
label: string;
|
|
25
29
|
primary: boolean;
|
|
26
30
|
enabled: boolean;
|
|
@@ -31,16 +35,19 @@ export type InspectedScreen = {
|
|
|
31
35
|
}>;
|
|
32
36
|
flows: Array<{
|
|
33
37
|
id: string;
|
|
38
|
+
semanticId: string;
|
|
34
39
|
name: string;
|
|
35
40
|
stepCount: number;
|
|
36
41
|
}>;
|
|
37
42
|
surfaces: Array<{
|
|
38
43
|
id: string;
|
|
44
|
+
semanticId: string;
|
|
39
45
|
name: string;
|
|
40
46
|
itemCount: number;
|
|
41
47
|
}>;
|
|
42
48
|
resources: Array<{
|
|
43
49
|
id: string;
|
|
50
|
+
semanticId: string;
|
|
44
51
|
name: string;
|
|
45
52
|
status: string;
|
|
46
53
|
hasValue: boolean;
|
package/dist/index.js
CHANGED
|
@@ -351,6 +351,11 @@ function getSurfaces() {
|
|
|
351
351
|
function getResources() {
|
|
352
352
|
return resourceMap;
|
|
353
353
|
}
|
|
354
|
+
function nextSuffix(baseId, exists) {
|
|
355
|
+
let counter = 2;
|
|
356
|
+
while (exists(`${baseId}-${counter}`)) counter++;
|
|
357
|
+
return `${baseId}-${counter}`;
|
|
358
|
+
}
|
|
354
359
|
|
|
355
360
|
//#endregion
|
|
356
361
|
//#region src/ask.ts
|
|
@@ -408,7 +413,9 @@ function computeAskError(node) {
|
|
|
408
413
|
var AskBuilder = class {
|
|
409
414
|
node;
|
|
410
415
|
constructor(label, stateRef) {
|
|
411
|
-
const
|
|
416
|
+
const baseId = `ask_${label.toLowerCase().replace(/\s+/g, "_")}`;
|
|
417
|
+
const existing = getAsks();
|
|
418
|
+
const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
|
|
412
419
|
const onChange = stateRef.onChange;
|
|
413
420
|
const subscribeToState = onChange ? (fn) => onChange((_v) => fn()) : void 0;
|
|
414
421
|
this.node = createAskNode(id, label, stateRef, subscribeToState);
|
|
@@ -520,7 +527,9 @@ async function executeAct(node, context, notify) {
|
|
|
520
527
|
var ActBuilder = class {
|
|
521
528
|
node;
|
|
522
529
|
constructor(label) {
|
|
523
|
-
const
|
|
530
|
+
const baseId = `act_${label.toLowerCase().replace(/\s+/g, "_")}`;
|
|
531
|
+
const existing = getActs();
|
|
532
|
+
const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
|
|
524
533
|
this.node = createActNode(id, label, [], null, void 0, false);
|
|
525
534
|
registerActNode(this.node);
|
|
526
535
|
}
|
|
@@ -578,7 +587,9 @@ var ActBuilder = class {
|
|
|
578
587
|
var FlowBuilder = class {
|
|
579
588
|
node;
|
|
580
589
|
constructor(name) {
|
|
581
|
-
const
|
|
590
|
+
const baseId = `flow_${name}`;
|
|
591
|
+
const existing = getFlows();
|
|
592
|
+
const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
|
|
582
593
|
this.node = {
|
|
583
594
|
id,
|
|
584
595
|
name,
|
|
@@ -623,7 +634,9 @@ var FlowBuilder = class {
|
|
|
623
634
|
var SurfaceBuilder = class {
|
|
624
635
|
node;
|
|
625
636
|
constructor(name) {
|
|
626
|
-
const
|
|
637
|
+
const baseId = `surface_${name}`;
|
|
638
|
+
const existing = getSurfaces();
|
|
639
|
+
const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
|
|
627
640
|
this.node = {
|
|
628
641
|
id,
|
|
629
642
|
name,
|
|
@@ -662,7 +675,8 @@ function screen(name, fn) {
|
|
|
662
675
|
flow: (n) => new FlowBuilder(n),
|
|
663
676
|
surface: (n) => new SurfaceBuilder(n),
|
|
664
677
|
resource: (n, config) => {
|
|
665
|
-
const
|
|
678
|
+
const baseId = `resource_${n}`;
|
|
679
|
+
const id = configs.some((c) => c.id === baseId) ? nextSuffix(baseId, (id$1) => configs.some((c) => c.id === id$1)) : baseId;
|
|
666
680
|
const ref = new ResourceRef(id, n, config.load, config.autoLoad ?? true);
|
|
667
681
|
configs.push({
|
|
668
682
|
id,
|
|
@@ -691,6 +705,28 @@ function screen(name, fn) {
|
|
|
691
705
|
|
|
692
706
|
//#endregion
|
|
693
707
|
//#region src/graph.ts
|
|
708
|
+
const NODE_KINDS = {
|
|
709
|
+
ask: "ask",
|
|
710
|
+
act: "action",
|
|
711
|
+
flow: "flow",
|
|
712
|
+
surface: "surface",
|
|
713
|
+
resource: "resource"
|
|
714
|
+
};
|
|
715
|
+
function slugify(text) {
|
|
716
|
+
return text.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
717
|
+
}
|
|
718
|
+
function createSemanticIdFactory(kind) {
|
|
719
|
+
const prefix = NODE_KINDS[kind] ?? kind;
|
|
720
|
+
const used = /* @__PURE__ */ new Map();
|
|
721
|
+
let unnamed = 0;
|
|
722
|
+
return (source) => {
|
|
723
|
+
const slug = slugify(source);
|
|
724
|
+
const base = slug.length > 0 ? slug : String(++unnamed);
|
|
725
|
+
const count = used.get(base) ?? 0;
|
|
726
|
+
used.set(base, count + 1);
|
|
727
|
+
return count === 0 ? `${prefix}:${base}` : `${prefix}:${base}-${count + 1}`;
|
|
728
|
+
};
|
|
729
|
+
}
|
|
694
730
|
function computeDiagnostics(screenDef) {
|
|
695
731
|
const diagnostics = [];
|
|
696
732
|
const primaryActions = screenDef.acts.filter((a) => a.primary);
|
|
@@ -725,13 +761,44 @@ function computeDiagnostics(screenDef) {
|
|
|
725
761
|
message: "Action is defined but not included in any surface.",
|
|
726
762
|
nodeId: act.id
|
|
727
763
|
});
|
|
764
|
+
if (screenDef.flows.length > 0) {
|
|
765
|
+
const flowNodeIds = /* @__PURE__ */ new Set();
|
|
766
|
+
for (const flow of screenDef.flows) for (const step of flow.steps) flowNodeIds.add(step.node.id);
|
|
767
|
+
for (const ask of screenDef.asks) if (surfacedNodeIds.has(ask.id) && !flowNodeIds.has(ask.id)) diagnostics.push({
|
|
768
|
+
severity: "info",
|
|
769
|
+
code: "surfaced-node-not-in-any-flow",
|
|
770
|
+
message: `"${ask.label}" is surfaced but not referenced in any flow.`,
|
|
771
|
+
nodeId: ask.id
|
|
772
|
+
});
|
|
773
|
+
for (const act of screenDef.acts) if (surfacedNodeIds.has(act.id) && !flowNodeIds.has(act.id)) diagnostics.push({
|
|
774
|
+
severity: "info",
|
|
775
|
+
code: "surfaced-node-not-in-any-flow",
|
|
776
|
+
message: `"${act.label}" is surfaced but not referenced in any flow.`,
|
|
777
|
+
nodeId: act.id
|
|
778
|
+
});
|
|
779
|
+
}
|
|
728
780
|
return diagnostics;
|
|
729
781
|
}
|
|
730
782
|
function inspectScreen(screenDef, runtimeResources) {
|
|
783
|
+
const diagnostics = computeDiagnostics(screenDef);
|
|
784
|
+
const askIds = createSemanticIdFactory("ask");
|
|
785
|
+
const actIds = createSemanticIdFactory("act");
|
|
786
|
+
const flowIds = createSemanticIdFactory("flow");
|
|
787
|
+
const surfaceIds = createSemanticIdFactory("surface");
|
|
788
|
+
const resourceIds = createSemanticIdFactory("resource");
|
|
789
|
+
const idToSemantic = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const a of screenDef.asks) idToSemantic.set(a.id, askIds(a.label));
|
|
791
|
+
for (const a of screenDef.acts) idToSemantic.set(a.id, actIds(a.label));
|
|
792
|
+
const augmentedDiagnostics = diagnostics.map((d) => ({
|
|
793
|
+
...d,
|
|
794
|
+
semanticNodeId: d.nodeId ? idToSemantic.get(d.nodeId) : void 0
|
|
795
|
+
}));
|
|
731
796
|
return {
|
|
732
797
|
name: screenDef.name,
|
|
798
|
+
semanticId: `screen:${slugify(screenDef.name)}`,
|
|
733
799
|
asks: screenDef.asks.map((a) => ({
|
|
734
800
|
id: a.id,
|
|
801
|
+
semanticId: idToSemantic.get(a.id),
|
|
735
802
|
label: a.label,
|
|
736
803
|
kind: a.kind,
|
|
737
804
|
required: a.required,
|
|
@@ -741,6 +808,7 @@ function inspectScreen(screenDef, runtimeResources) {
|
|
|
741
808
|
})),
|
|
742
809
|
acts: screenDef.acts.map((a) => ({
|
|
743
810
|
id: a.id,
|
|
811
|
+
semanticId: idToSemantic.get(a.id),
|
|
744
812
|
label: a.label,
|
|
745
813
|
primary: a.primary,
|
|
746
814
|
enabled: a.enabled.current,
|
|
@@ -751,23 +819,26 @@ function inspectScreen(screenDef, runtimeResources) {
|
|
|
751
819
|
})),
|
|
752
820
|
flows: screenDef.flows.map((f) => ({
|
|
753
821
|
id: f.id,
|
|
822
|
+
semanticId: flowIds(f.name),
|
|
754
823
|
name: f.name,
|
|
755
824
|
stepCount: f.steps.length
|
|
756
825
|
})),
|
|
757
826
|
surfaces: screenDef.surfaces.map((s) => ({
|
|
758
827
|
id: s.id,
|
|
828
|
+
semanticId: surfaceIds(s.name),
|
|
759
829
|
name: s.name,
|
|
760
830
|
itemCount: s.items.length
|
|
761
831
|
})),
|
|
762
832
|
resources: (runtimeResources ?? []).map((r) => ({
|
|
763
833
|
id: r.id,
|
|
834
|
+
semanticId: resourceIds(r.name),
|
|
764
835
|
name: r.name,
|
|
765
836
|
status: r.status,
|
|
766
837
|
hasValue: r.value !== void 0,
|
|
767
838
|
stale: r.stale.current,
|
|
768
839
|
error: r.status === "failed" ? r.error instanceof Error ? r.error.message : String(r.error) : void 0
|
|
769
840
|
})),
|
|
770
|
-
diagnostics:
|
|
841
|
+
diagnostics: augmentedDiagnostics
|
|
771
842
|
};
|
|
772
843
|
}
|
|
773
844
|
|
package/dist/registry.d.ts
CHANGED
|
@@ -23,3 +23,4 @@ export declare function getActs(): Map<string, ActNode<any>>;
|
|
|
23
23
|
export declare function getFlows(): Map<string, FlowNode>;
|
|
24
24
|
export declare function getSurfaces(): Map<string, SurfaceNode>;
|
|
25
25
|
export declare function getResources(): Map<string, AnyResourceNode>;
|
|
26
|
+
export declare function nextSuffix(baseId: string, exists: (id: string) => boolean): string;
|