@zhin.js/plugin-runtime 1.1.1 → 1.1.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.
@@ -1,3 +1,4 @@
1
+ import { type PluginId } from './identity.js';
1
2
  export interface DatabaseHostSelection {
2
3
  where(query: Record<string, unknown>): DatabaseHostSelection;
3
4
  orderBy(field: string, direction?: 'ASC' | 'DESC'): DatabaseHostSelection;
@@ -83,4 +84,43 @@ export interface DatabaseHost {
83
84
  start(): Promise<void>;
84
85
  stop(): Promise<void>;
85
86
  }
86
- export declare const databaseHostToken: import("./token.js").Token<DatabaseHost>;
87
+ /**
88
+ * Persistence surface visible to a plugin. Names are logical to the owner;
89
+ * the Runtime maps them to private physical table names before they reach the
90
+ * process-wide DatabaseHost.
91
+ */
92
+ export interface PluginDatabaseHost {
93
+ readonly owner: PluginId;
94
+ readonly dialect: string;
95
+ readonly started: boolean;
96
+ define(name: string, definition: Record<string, unknown>): void;
97
+ tables(): readonly string[];
98
+ models: {
99
+ get(name: string): DatabaseHostModel | undefined;
100
+ };
101
+ }
102
+ /**
103
+ * Maps a plugin's logical resource name to its process-wide physical name.
104
+ * Root keeps its historical bare names so existing projects do not need a
105
+ * database migration merely to adopt scoped child plugins.
106
+ */
107
+ export declare function qualifyPluginResourceName(owner: PluginId, name: string): string;
108
+ /** Reverse `qualifyPluginResourceName` only when the name belongs to owner. */
109
+ export declare function unqualifyPluginResourceName(owner: PluginId, name: string): string | undefined;
110
+ /** Creates a tenant facade without exposing Console administration or raw DB access. */
111
+ export declare function createPluginDatabaseHost(owner: PluginId, host: DatabaseHost): PluginDatabaseHost;
112
+ /**
113
+ * Recovers a process host from the standard root token or a scoped facade.
114
+ * This keeps custom RootRuntime installers written against the pre-facade
115
+ * token working while child scopes begin receiving tenant boundaries.
116
+ */
117
+ export declare function unwrapPluginDatabaseHost(host: DatabaseHost | PluginDatabaseHost): DatabaseHost | undefined;
118
+ /**
119
+ * Plugin-facing scoped persistence token. Runtime replaces this binding for
120
+ * every Plugin Scope; plugin code must never receive the process-wide host.
121
+ * The owner encoding keeps `root/a` and `root/a/b` disjoint, so a parent
122
+ * facade cannot accidentally enumerate a descendant's tables.
123
+ */
124
+ export declare const databaseHostToken: import("./token.js").Token<PluginDatabaseHost>;
125
+ /** Root-only process host for CLI composition and Console administration. */
126
+ export declare const databaseRootHostToken: import("./token.js").Token<DatabaseHost>;
@@ -1,2 +1,71 @@
1
+ import { pluginOwnerResourceKey, rootPluginId } from './identity.js';
1
2
  import { createToken } from './token.js';
2
- export const databaseHostToken = createToken('zhin.database.host', 'Plugin Runtime database host');
3
+ const resourcePrefix = '__zhin_plugin__';
4
+ const resourceSeparator = '__';
5
+ const roots = new WeakMap();
6
+ /**
7
+ * Maps a plugin's logical resource name to its process-wide physical name.
8
+ * Root keeps its historical bare names so existing projects do not need a
9
+ * database migration merely to adopt scoped child plugins.
10
+ */
11
+ export function qualifyPluginResourceName(owner, name) {
12
+ assertLogicalResourceName(name);
13
+ if (owner === rootPluginId())
14
+ return name;
15
+ return `${resourcePrefix}${pluginOwnerResourceKey(owner)}${resourceSeparator}${name}`;
16
+ }
17
+ /** Reverse `qualifyPluginResourceName` only when the name belongs to owner. */
18
+ export function unqualifyPluginResourceName(owner, name) {
19
+ if (owner === rootPluginId())
20
+ return name.startsWith(resourcePrefix) ? undefined : name;
21
+ const prefix = `${resourcePrefix}${pluginOwnerResourceKey(owner)}${resourceSeparator}`;
22
+ return name.startsWith(prefix) ? name.slice(prefix.length) : undefined;
23
+ }
24
+ /** Creates a tenant facade without exposing Console administration or raw DB access. */
25
+ export function createPluginDatabaseHost(owner, host) {
26
+ const facade = Object.freeze({
27
+ owner,
28
+ get dialect() { return host.dialect; },
29
+ get started() { return host.started; },
30
+ define(name, definition) {
31
+ host.define(qualifyPluginResourceName(owner, name), definition);
32
+ },
33
+ tables() {
34
+ return Object.freeze(host.tables().flatMap((name) => {
35
+ const logical = unqualifyPluginResourceName(owner, name);
36
+ return logical === undefined ? [] : [logical];
37
+ }));
38
+ },
39
+ models: Object.freeze({
40
+ get(name) {
41
+ return host.models.get(qualifyPluginResourceName(owner, name));
42
+ },
43
+ }),
44
+ });
45
+ roots.set(facade, host);
46
+ return facade;
47
+ }
48
+ /**
49
+ * Recovers a process host from the standard root token or a scoped facade.
50
+ * This keeps custom RootRuntime installers written against the pre-facade
51
+ * token working while child scopes begin receiving tenant boundaries.
52
+ */
53
+ export function unwrapPluginDatabaseHost(host) {
54
+ if ('getRawDatabase' in host)
55
+ return host;
56
+ return roots.get(host);
57
+ }
58
+ function assertLogicalResourceName(name) {
59
+ if (!name || name.startsWith(resourcePrefix)) {
60
+ throw new TypeError(`Invalid plugin resource name: ${name}`);
61
+ }
62
+ }
63
+ /**
64
+ * Plugin-facing scoped persistence token. Runtime replaces this binding for
65
+ * every Plugin Scope; plugin code must never receive the process-wide host.
66
+ * The owner encoding keeps `root/a` and `root/a/b` disjoint, so a parent
67
+ * facade cannot accidentally enumerate a descendant's tables.
68
+ */
69
+ export const databaseHostToken = createToken('zhin.database.host', 'Plugin Runtime scoped database host');
70
+ /** Root-only process host for CLI composition and Console administration. */
71
+ export const databaseRootHostToken = createToken('zhin.database.root-host', 'Plugin Runtime root database host');
package/lib/identity.d.ts CHANGED
@@ -18,5 +18,13 @@ export declare function tokenId(value: string): TokenId;
18
18
  export declare function featureId(value: string): FeatureId;
19
19
  export declare function rootPluginId(): PluginId;
20
20
  export declare function childPluginId(parent: PluginId, instanceKey: string): PluginId;
21
+ /**
22
+ * Database-safe owner encoding used by process-wide plugin resources.
23
+ *
24
+ * Each path segment is length-prefixed and the final underscore is followed
25
+ * by a resource separator. A parent owner therefore cannot match a child
26
+ * owner merely because their PluginId has a common path prefix.
27
+ */
28
+ export declare function pluginOwnerResourceKey(owner: PluginId): string;
21
29
  export declare function capabilityId(owner: PluginId, feature: FeatureId, localName: string): CapabilityId;
22
30
  export {};
package/lib/identity.js CHANGED
@@ -24,6 +24,19 @@ export function childPluginId(parent, instanceKey) {
24
24
  }
25
25
  return `${parent}/${instanceKey}`;
26
26
  }
27
+ /**
28
+ * Database-safe owner encoding used by process-wide plugin resources.
29
+ *
30
+ * Each path segment is length-prefixed and the final underscore is followed
31
+ * by a resource separator. A parent owner therefore cannot match a child
32
+ * owner merely because their PluginId has a common path prefix.
33
+ */
34
+ export function pluginOwnerResourceKey(owner) {
35
+ return `${String(owner)
36
+ .split('/')
37
+ .map((segment) => `${segment.length}_${segment}`)
38
+ .join('_')}_`;
39
+ }
27
40
  export function capabilityId(owner, feature, localName) {
28
41
  if (!capabilityLocalNamePattern.test(localName)) {
29
42
  throw new TypeError(`Invalid capability local name: ${localName}`);
@@ -1,9 +1,12 @@
1
+ import { type PluginId } from './identity.js';
1
2
  /**
2
3
  * Thin Host Resource for Plugin Runtime cron jobs.
3
4
  * Implementations typically wrap `@zhin.js/schedule` CalendarScheduler (solar cron).
4
5
  */
5
6
  export interface ScheduleJobRegistration {
6
7
  readonly id: string;
8
+ /** Runtime-assigned owner for diagnostics and Console inventory. */
9
+ readonly owner?: PluginId;
7
10
  /** 6-field solar cron: `秒 分 时 日 月 周` */
8
11
  readonly cron: string;
9
12
  readonly description?: string;
@@ -16,6 +19,25 @@ export interface ScheduleHost {
16
19
  readonly id: string;
17
20
  readonly cron: string;
18
21
  readonly description?: string;
22
+ readonly owner?: PluginId;
19
23
  }[];
20
24
  }
21
- export declare const scheduleHostToken: import("./token.js").Token<ScheduleHost>;
25
+ /** Scoped scheduling surface exposed to a Plugin setup function. */
26
+ export interface PluginScheduleHost {
27
+ readonly owner: PluginId;
28
+ register(job: ScheduleJobRegistration): () => void;
29
+ list(): readonly {
30
+ readonly id: string;
31
+ readonly cron: string;
32
+ readonly description?: string;
33
+ }[];
34
+ }
35
+ export declare function qualifyPluginScheduleId(owner: PluginId, id: string): string;
36
+ export declare function unqualifyPluginScheduleId(owner: PluginId, id: string): string | undefined;
37
+ export declare function createPluginScheduleHost(owner: PluginId, host: ScheduleHost): PluginScheduleHost;
38
+ /** See unwrapPluginDatabaseHost: preserves legacy custom RootResource installers. */
39
+ export declare function unwrapPluginScheduleHost(host: ScheduleHost | PluginScheduleHost): ScheduleHost;
40
+ /** Plugin-facing token; Runtime injects a facade bound to the current owner. */
41
+ export declare const scheduleHostToken: import("./token.js").Token<PluginScheduleHost>;
42
+ /** Root-only process scheduler for CLI composition and Console administration. */
43
+ export declare const scheduleRootHostToken: import("./token.js").Token<ScheduleHost>;
@@ -1,2 +1,52 @@
1
+ import { pluginOwnerResourceKey, rootPluginId } from './identity.js';
1
2
  import { createToken } from './token.js';
2
- export const scheduleHostToken = createToken('zhin.schedule.host', 'Plugin Runtime solar cron host');
3
+ const jobPrefix = '__zhin_plugin__';
4
+ const jobSeparator = '__';
5
+ const roots = new WeakMap();
6
+ export function qualifyPluginScheduleId(owner, id) {
7
+ assertLogicalJobId(id);
8
+ if (owner === rootPluginId())
9
+ return id;
10
+ return `${jobPrefix}${pluginOwnerResourceKey(owner)}${jobSeparator}${id}`;
11
+ }
12
+ export function unqualifyPluginScheduleId(owner, id) {
13
+ if (owner === rootPluginId())
14
+ return id.startsWith(jobPrefix) ? undefined : id;
15
+ const prefix = `${jobPrefix}${pluginOwnerResourceKey(owner)}${jobSeparator}`;
16
+ return id.startsWith(prefix) ? id.slice(prefix.length) : undefined;
17
+ }
18
+ export function createPluginScheduleHost(owner, host) {
19
+ const facade = Object.freeze({
20
+ owner,
21
+ register(job) {
22
+ return host.register({ ...job, id: qualifyPluginScheduleId(owner, job.id), owner });
23
+ },
24
+ list() {
25
+ return Object.freeze(host.list().flatMap((job) => {
26
+ const id = unqualifyPluginScheduleId(owner, job.id);
27
+ if (id === undefined)
28
+ return [];
29
+ return [Object.freeze({
30
+ id,
31
+ cron: job.cron,
32
+ ...(job.description === undefined ? {} : { description: job.description }),
33
+ })];
34
+ }));
35
+ },
36
+ });
37
+ roots.set(facade, host);
38
+ return facade;
39
+ }
40
+ /** See unwrapPluginDatabaseHost: preserves legacy custom RootResource installers. */
41
+ export function unwrapPluginScheduleHost(host) {
42
+ return roots.get(host) ?? host;
43
+ }
44
+ function assertLogicalJobId(id) {
45
+ if (!id || id.startsWith(jobPrefix)) {
46
+ throw new TypeError(`Invalid plugin schedule id: ${id}`);
47
+ }
48
+ }
49
+ /** Plugin-facing token; Runtime injects a facade bound to the current owner. */
50
+ export const scheduleHostToken = createToken('zhin.schedule.host', 'Plugin Runtime scoped solar cron host');
51
+ /** Root-only process scheduler for CLI composition and Console administration. */
52
+ export const scheduleRootHostToken = createToken('zhin.schedule.root-host', 'Plugin Runtime root solar cron host');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-runtime",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Zero-dependency Plugin tree, Scope and atomic generation runtime for Zhin",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -16,7 +16,7 @@
16
16
  "lib"
17
17
  ],
18
18
  "devDependencies": {
19
- "@types/node": "^26.1.0",
19
+ "@types/node": "^26.1.2",
20
20
  "typescript": "^6.0.3"
21
21
  },
22
22
  "engines": {