@proto.ui/module-base 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Proto UI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @proto.ui/module-base
2
+
3
+ Base package for building Proto UI modules.
4
+
5
+ ## Purpose
6
+
7
+ Provides the base template, shared capabilities, and development utilities for building Proto UI modules.
8
+
9
+ ## Package Role
10
+
11
+ Module foundation package used to implement adapter-facing modules in a consistent way.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @proto.ui/module-base@0.0.1
17
+ ```
18
+
19
+ ## Internal Structure
20
+
21
+ - `src/caps-vault/`
22
+ - `src/create-module.ts`
23
+ - `src/index.ts`
24
+ - `src/module-base.ts`
25
+ - `src/system-caps.ts`
26
+
27
+ ## Related Internal Packages
28
+
29
+ - `@proto.ui/core`
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './vault';
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './vault';
@@ -0,0 +1 @@
1
+ export type Unsubscribe = () => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { CapEntries, CapToken, CapsVaultView } from '@proto.ui/core';
2
+ import { Unsubscribe } from './types';
3
+ /**
4
+ * Mutable caps vault for runtime/host wiring.
5
+ *
6
+ * Two layers:
7
+ * - base: runtime-owned built-ins (SYS_CAP etc.), never reset by host wiring
8
+ * - attached: host/wiring provided caps, resettable
9
+ *
10
+ * NOTE:
11
+ * - module-* should depend on the *view* interface only (CapsVaultView from core),
12
+ * but in practice it's fine if they accept this class as long as they
13
+ * don't call attach/reset* (keep it a convention).
14
+ */
15
+ export declare class CapsVault implements CapsVaultView {
16
+ private base;
17
+ private attached;
18
+ private listeners;
19
+ epoch: number;
20
+ has(token: CapToken<any>): boolean;
21
+ get<T>(token: CapToken<T>): T;
22
+ onChange(cb: (epoch: number) => void): Unsubscribe;
23
+ /**
24
+ * runtime-only: inject built-in/system caps (SYS_CAP etc.)
25
+ * This layer survives host reset/unmount resets.
26
+ */
27
+ attachBase(entries: CapEntries): void;
28
+ /**
29
+ * wiring-only: host/runtime inject capabilities for this instance.
30
+ * This layer can be cleared by resetAttached().
31
+ */
32
+ attach(entries: CapEntries): void;
33
+ /**
34
+ * wiring-only: invalidate ONLY the attached layer.
35
+ * SYS_CAP (base) remains available.
36
+ */
37
+ resetAttached(): void;
38
+ /**
39
+ * runtime-only: invalidate everything (rare; prefer resetAttached()).
40
+ */
41
+ resetAll(): void;
42
+ private bump;
43
+ }
@@ -0,0 +1,101 @@
1
+ import { capUnavailable } from '@proto.ui/core';
2
+ /**
3
+ * Mutable caps vault for runtime/host wiring.
4
+ *
5
+ * Two layers:
6
+ * - base: runtime-owned built-ins (SYS_CAP etc.), never reset by host wiring
7
+ * - attached: host/wiring provided caps, resettable
8
+ *
9
+ * NOTE:
10
+ * - module-* should depend on the *view* interface only (CapsVaultView from core),
11
+ * but in practice it's fine if they accept this class as long as they
12
+ * don't call attach/reset* (keep it a convention).
13
+ */
14
+ export class CapsVault {
15
+ base = new Map();
16
+ attached = new Map();
17
+ listeners = new Set();
18
+ epoch = 0;
19
+ has(token) {
20
+ const id = token.id;
21
+ return this.attached.has(id) || this.base.has(id);
22
+ }
23
+ get(token) {
24
+ const id = token.id;
25
+ if (this.attached.has(id))
26
+ return this.attached.get(id);
27
+ if (this.base.has(id))
28
+ return this.base.get(id);
29
+ throw capUnavailable(id, { epoch: this.epoch });
30
+ }
31
+ onChange(cb) {
32
+ this.listeners.add(cb);
33
+ return () => this.listeners.delete(cb);
34
+ }
35
+ // -------------------------
36
+ // wiring APIs (mutable)
37
+ // -------------------------
38
+ /**
39
+ * runtime-only: inject built-in/system caps (SYS_CAP etc.)
40
+ * This layer survives host reset/unmount resets.
41
+ */
42
+ attachBase(entries) {
43
+ if (!entries || entries.length === 0)
44
+ return;
45
+ let changed = false;
46
+ for (const [token, value] of entries) {
47
+ const id = token.id;
48
+ const prev = this.base.get(id);
49
+ if (!this.base.has(id) || prev !== value) {
50
+ this.base.set(id, value);
51
+ changed = true;
52
+ }
53
+ }
54
+ if (changed)
55
+ this.bump();
56
+ }
57
+ /**
58
+ * wiring-only: host/runtime inject capabilities for this instance.
59
+ * This layer can be cleared by resetAttached().
60
+ */
61
+ attach(entries) {
62
+ if (!entries || entries.length === 0)
63
+ return;
64
+ let changed = false;
65
+ for (const [token, value] of entries) {
66
+ const id = token.id;
67
+ const prev = this.attached.get(id);
68
+ if (!this.attached.has(id) || prev !== value) {
69
+ this.attached.set(id, value);
70
+ changed = true;
71
+ }
72
+ }
73
+ if (changed)
74
+ this.bump();
75
+ }
76
+ /**
77
+ * wiring-only: invalidate ONLY the attached layer.
78
+ * SYS_CAP (base) remains available.
79
+ */
80
+ resetAttached() {
81
+ if (this.attached.size === 0)
82
+ return;
83
+ this.attached.clear();
84
+ this.bump();
85
+ }
86
+ /**
87
+ * runtime-only: invalidate everything (rare; prefer resetAttached()).
88
+ */
89
+ resetAll() {
90
+ if (this.attached.size === 0 && this.base.size === 0)
91
+ return;
92
+ this.attached.clear();
93
+ this.base.clear();
94
+ this.bump();
95
+ }
96
+ bump() {
97
+ this.epoch++;
98
+ for (const cb of this.listeners)
99
+ cb(this.epoch);
100
+ }
101
+ }
@@ -0,0 +1,72 @@
1
+ import type { ModuleFacade, ModuleHooks, ModuleInstance, ModuleInit, ModuleScope, CapsVaultView } from '@proto.ui/core';
2
+ export type ModuleDeps = {
3
+ /**
4
+ * Require a declared dependency's facade.
5
+ * Throws if the dependency is undeclared or missing.
6
+ */
7
+ requireFacade<T extends ModuleFacade>(name: string): T;
8
+ /**
9
+ * Require a declared dependency's port.
10
+ * Throws if the dependency is undeclared or missing.
11
+ */
12
+ requirePort<T>(name: string): T;
13
+ /**
14
+ * Try a declared dependency's facade.
15
+ * Returns undefined if missing, but still throws if undeclared.
16
+ */
17
+ tryFacade<T extends ModuleFacade>(name: string): T | undefined;
18
+ /**
19
+ * Try a declared dependency's port.
20
+ * Returns undefined if missing, but still throws if undeclared.
21
+ */
22
+ tryPort<T>(name: string): T | undefined;
23
+ };
24
+ export type ModuleFactoryArgs = {
25
+ init: ModuleInit;
26
+ caps: CapsVaultView;
27
+ deps: ModuleDeps;
28
+ };
29
+ /**
30
+ * Module definition for the orchestrator.
31
+ * Use this for explicit deps declaration and consistent module metadata.
32
+ */
33
+ export type ModuleDef<Name extends string = string> = {
34
+ name: Name;
35
+ /**
36
+ * Hard dependencies: must exist and be initialized first.
37
+ */
38
+ deps?: string[];
39
+ /**
40
+ * Optional dependencies: used if present, ignored if missing.
41
+ */
42
+ optionalDeps?: string[];
43
+ /**
44
+ * Module factory. Prefer small, deterministic construction.
45
+ */
46
+ create: (ctx: ModuleFactoryArgs) => ModuleInstance<ModuleFacade> & {
47
+ name: Name;
48
+ scope: ModuleScope;
49
+ port?: unknown;
50
+ };
51
+ };
52
+ /**
53
+ * Define a module with explicit deps.
54
+ * This is the preferred entrypoint for module registration.
55
+ */
56
+ export declare function defineModule<Name extends string>(def: ModuleDef<Name>): ModuleDef<Name>;
57
+ export declare function createModule<Name extends string, Scope extends ModuleScope, Facade extends ModuleFacade, Port = undefined>(args: {
58
+ name: Name;
59
+ scope: Scope;
60
+ init: ModuleInit;
61
+ caps: CapsVaultView;
62
+ deps: ModuleDeps;
63
+ build: (ctx: ModuleFactoryArgs) => {
64
+ facade: Facade;
65
+ hooks?: ModuleHooks;
66
+ port?: Port;
67
+ };
68
+ }): ModuleInstance<Facade> & {
69
+ name: Name;
70
+ scope: Scope;
71
+ port?: Port;
72
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Define a module with explicit deps.
3
+ * This is the preferred entrypoint for module registration.
4
+ */
5
+ export function defineModule(def) {
6
+ return def;
7
+ }
8
+ export function createModule(args) {
9
+ const { facade, hooks, port } = args.build({
10
+ init: args.init,
11
+ caps: args.caps,
12
+ deps: args.deps,
13
+ });
14
+ return {
15
+ name: args.name,
16
+ scope: args.scope,
17
+ facade,
18
+ hooks: hooks ?? {},
19
+ port,
20
+ };
21
+ }
@@ -0,0 +1,4 @@
1
+ export * from './caps-vault';
2
+ export * from './module-base';
3
+ export * from './create-module';
4
+ export * from './system-caps';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // packages/modules/base/src/index.ts
2
+ export * from './caps-vault';
3
+ export * from './module-base';
4
+ export * from './create-module';
5
+ export * from './system-caps';
@@ -0,0 +1,13 @@
1
+ import type { ProtoPhase } from '@proto.ui/core';
2
+ import type { CapsVaultView } from '@proto.ui/core';
3
+ export declare abstract class ModuleBase {
4
+ protected protoPhase: ProtoPhase;
5
+ protected readonly caps: CapsVaultView;
6
+ private pending;
7
+ constructor(caps: CapsVaultView);
8
+ protected get sys(): import("./system-caps").SystemCaps;
9
+ onProtoPhase(phase: ProtoPhase): void;
10
+ protected onCapsEpoch(_epoch: number): void;
11
+ protected defer(fn: () => void): void;
12
+ protected flushPending(): void;
13
+ }
@@ -0,0 +1,31 @@
1
+ import { SYS_CAP } from './system-caps';
2
+ export class ModuleBase {
3
+ protoPhase = 'setup';
4
+ caps;
5
+ pending = [];
6
+ constructor(caps) {
7
+ this.caps = caps;
8
+ this.caps.onChange((epoch) => {
9
+ this.onCapsEpoch(epoch);
10
+ this.flushPending();
11
+ });
12
+ }
13
+ get sys() {
14
+ return this.caps.get(SYS_CAP);
15
+ }
16
+ onProtoPhase(phase) {
17
+ this.protoPhase = phase;
18
+ }
19
+ onCapsEpoch(_epoch) { }
20
+ defer(fn) {
21
+ this.pending.push(fn);
22
+ }
23
+ flushPending() {
24
+ if (this.pending.length === 0)
25
+ return;
26
+ const tasks = this.pending;
27
+ this.pending = [];
28
+ for (const t of tasks)
29
+ t();
30
+ }
31
+ }
@@ -0,0 +1,32 @@
1
+ import { type ProtoPhase } from '@proto.ui/core';
2
+ export type ExecPhase = 'setup' | 'render' | 'callback' | 'unknown';
3
+ export type GuardDomain = 'setup' | 'runtime';
4
+ export interface SystemCaps {
5
+ /** exec-phase (more precise than domain) */
6
+ execPhase(): ExecPhase;
7
+ /** derived: setup vs runtime (kept for compatibility) */
8
+ domain(): GuardDomain;
9
+ /** proto lifecycle phase */
10
+ protoPhase(): ProtoPhase;
11
+ /** disposal state */
12
+ isDisposed(): boolean;
13
+ ensureNotDisposed(op: string): void;
14
+ ensureExecPhase(op: string, expected: ExecPhase | ExecPhase[]): void;
15
+ /** convenience */
16
+ ensureSetup(op: string): void;
17
+ ensureRuntime(op: string): void;
18
+ /**
19
+ * callback-only: recommended for "runtime mutation" APIs (state.set etc.)
20
+ * This prevents render-phase mutations.
21
+ */
22
+ ensureCallback(op: string): void;
23
+ /**
24
+ * Runtime callback context (opaque).
25
+ *
26
+ * - In engine-driven execution, this will typically be `run`.
27
+ * - Outside callback phase, it should return `undefined`.
28
+ * - State/event modules MUST treat it as unknown and not depend on its shape.
29
+ */
30
+ getCallbackCtx(): unknown;
31
+ }
32
+ export declare const SYS_CAP: import("@proto.ui/core").CapToken<SystemCaps>;
@@ -0,0 +1,2 @@
1
+ import { cap } from '@proto.ui/core';
2
+ export const SYS_CAP = cap('@proto.ui/__sys');
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@proto.ui/module-base",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@proto.ui/core": "0.0.1"
7
+ },
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "description": "Base package for building Proto UI modules.",
15
+ "license": "MIT",
16
+ "homepage": "https://github.com/guangliang2019/Prototype-UI/tree/main/packages/modules/base",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/guangliang2019/Prototype-UI.git",
20
+ "directory": "packages/modules/base"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/guangliang2019/Prototype-UI/issues"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "keywords": [
29
+ "proto-ui",
30
+ "proto",
31
+ "ui",
32
+ "module",
33
+ "base"
34
+ ],
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "LICENSE"
39
+ ]
40
+ }