sh3-core 0.9.1 → 0.10.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/dist/api.d.ts +1 -0
- package/dist/contributions/index.d.ts +2 -0
- package/dist/contributions/index.js +8 -0
- package/dist/contributions/registry.d.ts +21 -0
- package/dist/contributions/registry.js +89 -0
- package/dist/contributions/registry.test.d.ts +1 -0
- package/dist/contributions/registry.test.js +109 -0
- package/dist/contributions/types.d.ts +24 -0
- package/dist/contributions/types.js +10 -0
- package/dist/shards/activate-contributions.test.d.ts +1 -0
- package/dist/shards/activate-contributions.test.js +110 -0
- package/dist/shards/activate.svelte.js +23 -0
- package/dist/shards/types.d.ts +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focu
|
|
|
19
19
|
export type { DocumentHandle, DocumentHandleOptions, DocumentFormat, DocumentMeta, DocumentChange, AutosaveController, } from './documents/types';
|
|
20
20
|
export { PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ, PERMISSION_DOCUMENTS_WRITE, } from './documents/types';
|
|
21
21
|
export type { BrowseCapability } from './documents/browse';
|
|
22
|
+
export type { ContributionsApi } from './contributions/types';
|
|
22
23
|
export type { SyncPolicy, SyncPolicyRule, DocStatus, ConflictFile, ConflictBranch, } from './documents/sync-types';
|
|
23
24
|
export { PERMISSION_SYNC_PEER, PERMISSION_SYNC_POLICY } from './documents/sync-types';
|
|
24
25
|
export { registeredShards, activeShards } from './shards/activate.svelte';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Contribution-points barrel — internal re-exports.
|
|
3
|
+
*
|
|
4
|
+
* The public type (ContributionsApi) reaches shards via api.ts; this
|
|
5
|
+
* file is internal-only, re-exporting the registry for activate.svelte.ts
|
|
6
|
+
* and for tests.
|
|
7
|
+
*/
|
|
8
|
+
export { register, list, listPoints, onChange, __resetContributionsForTest } from './registry';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register a descriptor under the given point. Returns an unregister
|
|
3
|
+
* function; calling it more than once is a safe no-op.
|
|
4
|
+
*/
|
|
5
|
+
export declare function register<T = unknown>(pointId: string, descriptor: T): () => void;
|
|
6
|
+
/** Enumerate descriptors at the named point in registration order. */
|
|
7
|
+
export declare function list<T = unknown>(pointId: string): T[];
|
|
8
|
+
/** Enumerate every point id with at least one registration. */
|
|
9
|
+
export declare function listPoints(): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to registration changes at the named point. The callback
|
|
12
|
+
* receives no arguments — subscribers call `list` themselves to read
|
|
13
|
+
* the current state. Returns an unsubscribe; double-unsubscribe is a
|
|
14
|
+
* safe no-op.
|
|
15
|
+
*/
|
|
16
|
+
export declare function onChange(pointId: string, cb: () => void): () => void;
|
|
17
|
+
/**
|
|
18
|
+
* Test-only reset. Not exported from the barrel; tests import it
|
|
19
|
+
* directly from this module.
|
|
20
|
+
*/
|
|
21
|
+
export declare function __resetContributionsForTest(): void;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Contribution point registry — shard-to-shard runtime collaboration.
|
|
3
|
+
*
|
|
4
|
+
* One module-level map holds every point's descriptors. Handles are
|
|
5
|
+
* Symbols so two registrations with identical content stay distinct.
|
|
6
|
+
* Emitters are a Set of zero-arg callbacks; subscribers re-read `list`
|
|
7
|
+
* themselves when notified.
|
|
8
|
+
*
|
|
9
|
+
* No tenant partitioning: the shell is single-tenant per session; a
|
|
10
|
+
* future multi-tenant client would add a tenant dimension to the
|
|
11
|
+
* outer map (see direction spec §5.2).
|
|
12
|
+
*/
|
|
13
|
+
const points = new Map();
|
|
14
|
+
const listeners = new Map();
|
|
15
|
+
function emit(pointId) {
|
|
16
|
+
const set = listeners.get(pointId);
|
|
17
|
+
if (!set)
|
|
18
|
+
return;
|
|
19
|
+
for (const cb of set)
|
|
20
|
+
cb();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register a descriptor under the given point. Returns an unregister
|
|
24
|
+
* function; calling it more than once is a safe no-op.
|
|
25
|
+
*/
|
|
26
|
+
export function register(pointId, descriptor) {
|
|
27
|
+
const handle = Symbol();
|
|
28
|
+
let map = points.get(pointId);
|
|
29
|
+
if (!map) {
|
|
30
|
+
map = new Map();
|
|
31
|
+
points.set(pointId, map);
|
|
32
|
+
}
|
|
33
|
+
map.set(handle, descriptor);
|
|
34
|
+
emit(pointId);
|
|
35
|
+
let disposed = false;
|
|
36
|
+
return () => {
|
|
37
|
+
if (disposed)
|
|
38
|
+
return;
|
|
39
|
+
disposed = true;
|
|
40
|
+
const m = points.get(pointId);
|
|
41
|
+
if (!m)
|
|
42
|
+
return;
|
|
43
|
+
if (m.delete(handle)) {
|
|
44
|
+
if (m.size === 0)
|
|
45
|
+
points.delete(pointId);
|
|
46
|
+
emit(pointId);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Enumerate descriptors at the named point in registration order. */
|
|
51
|
+
export function list(pointId) {
|
|
52
|
+
const m = points.get(pointId);
|
|
53
|
+
return m ? Array.from(m.values()) : [];
|
|
54
|
+
}
|
|
55
|
+
/** Enumerate every point id with at least one registration. */
|
|
56
|
+
export function listPoints() {
|
|
57
|
+
return Array.from(points.keys());
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Subscribe to registration changes at the named point. The callback
|
|
61
|
+
* receives no arguments — subscribers call `list` themselves to read
|
|
62
|
+
* the current state. Returns an unsubscribe; double-unsubscribe is a
|
|
63
|
+
* safe no-op.
|
|
64
|
+
*/
|
|
65
|
+
export function onChange(pointId, cb) {
|
|
66
|
+
let set = listeners.get(pointId);
|
|
67
|
+
if (!set) {
|
|
68
|
+
set = new Set();
|
|
69
|
+
listeners.set(pointId, set);
|
|
70
|
+
}
|
|
71
|
+
set.add(cb);
|
|
72
|
+
let disposed = false;
|
|
73
|
+
return () => {
|
|
74
|
+
if (disposed)
|
|
75
|
+
return;
|
|
76
|
+
disposed = true;
|
|
77
|
+
set.delete(cb);
|
|
78
|
+
if (set.size === 0)
|
|
79
|
+
listeners.delete(pointId);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Test-only reset. Not exported from the barrel; tests import it
|
|
84
|
+
* directly from this module.
|
|
85
|
+
*/
|
|
86
|
+
export function __resetContributionsForTest() {
|
|
87
|
+
points.clear();
|
|
88
|
+
listeners.clear();
|
|
89
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { register, list, listPoints, onChange, __resetContributionsForTest, } from './registry';
|
|
3
|
+
describe('contributions registry', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
__resetContributionsForTest();
|
|
6
|
+
});
|
|
7
|
+
describe('register / list', () => {
|
|
8
|
+
it('returns an unregister function', () => {
|
|
9
|
+
const unreg = register('p', { id: 'a' });
|
|
10
|
+
expect(typeof unreg).toBe('function');
|
|
11
|
+
});
|
|
12
|
+
it('register then list returns the descriptor', () => {
|
|
13
|
+
register('p', { id: 'a' });
|
|
14
|
+
expect(list('p')).toEqual([{ id: 'a' }]);
|
|
15
|
+
});
|
|
16
|
+
it('lists multiple descriptors in registration order', () => {
|
|
17
|
+
register('p', { id: 'a' });
|
|
18
|
+
register('p', { id: 'b' });
|
|
19
|
+
register('p', { id: 'c' });
|
|
20
|
+
expect(list('p').map((d) => d.id)).toEqual(['a', 'b', 'c']);
|
|
21
|
+
});
|
|
22
|
+
it('separates descriptors by pointId', () => {
|
|
23
|
+
register('p1', { id: 'a' });
|
|
24
|
+
register('p2', { id: 'b' });
|
|
25
|
+
expect(list('p1')).toEqual([{ id: 'a' }]);
|
|
26
|
+
expect(list('p2')).toEqual([{ id: 'b' }]);
|
|
27
|
+
});
|
|
28
|
+
it('returns an empty array for an unknown pointId', () => {
|
|
29
|
+
expect(list('nope')).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
it('unregister removes the descriptor', () => {
|
|
32
|
+
const unreg = register('p', { id: 'a' });
|
|
33
|
+
unreg();
|
|
34
|
+
expect(list('p')).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
it('double-unregister is a no-op', () => {
|
|
37
|
+
const unreg = register('p', { id: 'a' });
|
|
38
|
+
unreg();
|
|
39
|
+
unreg();
|
|
40
|
+
expect(list('p')).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
it('allows duplicate descriptors (framework does not inspect content)', () => {
|
|
43
|
+
register('p', { id: 'a' });
|
|
44
|
+
register('p', { id: 'a' });
|
|
45
|
+
expect(list('p')).toHaveLength(2);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('listPoints', () => {
|
|
49
|
+
it('returns empty when nothing is registered', () => {
|
|
50
|
+
expect(listPoints()).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
it('returns every pointId with at least one registration', () => {
|
|
53
|
+
register('p1', { id: 'a' });
|
|
54
|
+
register('p2', { id: 'b' });
|
|
55
|
+
expect(listPoints().sort()).toEqual(['p1', 'p2']);
|
|
56
|
+
});
|
|
57
|
+
it('excludes pointIds whose last registration was unregistered', () => {
|
|
58
|
+
const u1 = register('p1', { id: 'a' });
|
|
59
|
+
register('p2', { id: 'b' });
|
|
60
|
+
u1();
|
|
61
|
+
expect(listPoints()).toEqual(['p2']);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('onChange', () => {
|
|
65
|
+
it('fires on register', () => {
|
|
66
|
+
const cb = vi.fn();
|
|
67
|
+
onChange('p', cb);
|
|
68
|
+
register('p', { id: 'a' });
|
|
69
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
70
|
+
});
|
|
71
|
+
it('fires on unregister', () => {
|
|
72
|
+
const cb = vi.fn();
|
|
73
|
+
const unreg = register('p', { id: 'a' });
|
|
74
|
+
onChange('p', cb);
|
|
75
|
+
unreg();
|
|
76
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
77
|
+
});
|
|
78
|
+
it('does not fire for other pointIds', () => {
|
|
79
|
+
const cb = vi.fn();
|
|
80
|
+
onChange('p1', cb);
|
|
81
|
+
register('p2', { id: 'a' });
|
|
82
|
+
expect(cb).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
it('supports multiple subscribers', () => {
|
|
85
|
+
const a = vi.fn();
|
|
86
|
+
const b = vi.fn();
|
|
87
|
+
onChange('p', a);
|
|
88
|
+
onChange('p', b);
|
|
89
|
+
register('p', { id: 'x' });
|
|
90
|
+
expect(a).toHaveBeenCalledTimes(1);
|
|
91
|
+
expect(b).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
it('unsubscribe stops callbacks', () => {
|
|
94
|
+
const cb = vi.fn();
|
|
95
|
+
const off = onChange('p', cb);
|
|
96
|
+
off();
|
|
97
|
+
register('p', { id: 'x' });
|
|
98
|
+
expect(cb).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
it('double-unsubscribe is a no-op', () => {
|
|
101
|
+
const cb = vi.fn();
|
|
102
|
+
const off = onChange('p', cb);
|
|
103
|
+
off();
|
|
104
|
+
off();
|
|
105
|
+
register('p', { id: 'x' });
|
|
106
|
+
expect(cb).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ContributionsApi {
|
|
2
|
+
/**
|
|
3
|
+
* Register `descriptor` under `pointId`. Descriptors are freeform;
|
|
4
|
+
* the framework does not inspect them. The type parameter exists
|
|
5
|
+
* for ergonomics — provider and contributor agree on the shape via
|
|
6
|
+
* a type-only import of the provider's public types.
|
|
7
|
+
*
|
|
8
|
+
* Returns an unregister function. Calling it is optional (the
|
|
9
|
+
* framework auto-unregisters on shard deactivate) and safe to call
|
|
10
|
+
* more than once.
|
|
11
|
+
*/
|
|
12
|
+
register<T = unknown>(pointId: string, descriptor: T): () => void;
|
|
13
|
+
/** Enumerate descriptors at `pointId` in registration order. */
|
|
14
|
+
list<T = unknown>(pointId: string): T[];
|
|
15
|
+
/** Enumerate every point id with at least one registration. */
|
|
16
|
+
listPoints(): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Subscribe to registration changes at `pointId`. The callback
|
|
19
|
+
* receives no arguments — call `list` from inside to read the
|
|
20
|
+
* current state. Returns an unsubscribe; auto-unsubscribed on
|
|
21
|
+
* shard deactivate.
|
|
22
|
+
*/
|
|
23
|
+
onChange(pointId: string, cb: () => void): () => void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ContributionsApi — the per-shard surface exposed on ShardContext.
|
|
3
|
+
*
|
|
4
|
+
* See docs/sh3-rfcs/2026-04-20-shard-contribution-points.md for
|
|
5
|
+
* motivation and semantics. Every registration and every onChange
|
|
6
|
+
* subscription made through this API is auto-cleaned when the owning
|
|
7
|
+
* shard deactivates; callers should not need to call the returned
|
|
8
|
+
* disposer unless they want to release early.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
+
import { __setDocumentBackend, __setTenantId } from '../documents/config';
|
|
4
|
+
import { registerShard, activateShard, deactivateShard, __resetShardRegistryForTest, } from './activate.svelte';
|
|
5
|
+
import { __resetContributionsForTest, list, listPoints } from '../contributions';
|
|
6
|
+
describe('ctx.contributions', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
__resetShardRegistryForTest();
|
|
9
|
+
__resetContributionsForTest();
|
|
10
|
+
__setDocumentBackend(new MemoryDocumentBackend());
|
|
11
|
+
__setTenantId('tenant-a');
|
|
12
|
+
});
|
|
13
|
+
it('is always present on ShardContext (no permission required)', async () => {
|
|
14
|
+
let captured = null;
|
|
15
|
+
registerShard({
|
|
16
|
+
manifest: { id: 'a', label: 'A', version: '0.0.0', views: [] },
|
|
17
|
+
activate(ctx) { captured = ctx; },
|
|
18
|
+
});
|
|
19
|
+
await activateShard('a');
|
|
20
|
+
expect(typeof captured.contributions.register).toBe('function');
|
|
21
|
+
expect(typeof captured.contributions.list).toBe('function');
|
|
22
|
+
expect(typeof captured.contributions.listPoints).toBe('function');
|
|
23
|
+
expect(typeof captured.contributions.onChange).toBe('function');
|
|
24
|
+
});
|
|
25
|
+
it('register() makes the descriptor visible via the global list()', async () => {
|
|
26
|
+
registerShard({
|
|
27
|
+
manifest: { id: 'provider', label: 'P', version: '0.0.0', views: [] },
|
|
28
|
+
activate(ctx) {
|
|
29
|
+
ctx.contributions.register('my.point', { id: 'alpha' });
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
await activateShard('provider');
|
|
33
|
+
expect(list('my.point')).toEqual([{ id: 'alpha' }]);
|
|
34
|
+
});
|
|
35
|
+
it('deactivate auto-unregisters every descriptor the shard registered', async () => {
|
|
36
|
+
registerShard({
|
|
37
|
+
manifest: { id: 'provider', label: 'P', version: '0.0.0', views: [] },
|
|
38
|
+
activate(ctx) {
|
|
39
|
+
ctx.contributions.register('p1', { id: 'a' });
|
|
40
|
+
ctx.contributions.register('p1', { id: 'b' });
|
|
41
|
+
ctx.contributions.register('p2', { id: 'c' });
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
await activateShard('provider');
|
|
45
|
+
expect(list('p1')).toHaveLength(2);
|
|
46
|
+
expect(list('p2')).toHaveLength(1);
|
|
47
|
+
deactivateShard('provider');
|
|
48
|
+
expect(list('p1')).toEqual([]);
|
|
49
|
+
expect(list('p2')).toEqual([]);
|
|
50
|
+
expect(listPoints()).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
it('deactivate auto-unsubscribes every onChange listener the shard registered', async () => {
|
|
53
|
+
const cb = vi.fn();
|
|
54
|
+
registerShard({
|
|
55
|
+
manifest: { id: 'watcher', label: 'W', version: '0.0.0', views: [] },
|
|
56
|
+
activate(ctx) {
|
|
57
|
+
ctx.contributions.onChange('p', cb);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
await activateShard('watcher');
|
|
61
|
+
registerShard({
|
|
62
|
+
manifest: { id: 'provider', label: 'P', version: '0.0.0', views: [] },
|
|
63
|
+
activate(ctx) {
|
|
64
|
+
ctx.contributions.register('p', { id: 'x' });
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
await activateShard('provider');
|
|
68
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
69
|
+
deactivateShard('watcher');
|
|
70
|
+
// Further registrations must not reach the watcher's callback.
|
|
71
|
+
registerShard({
|
|
72
|
+
manifest: { id: 'provider-2', label: 'P2', version: '0.0.0', views: [] },
|
|
73
|
+
activate(ctx) {
|
|
74
|
+
ctx.contributions.register('p', { id: 'y' });
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
await activateShard('provider-2');
|
|
78
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
79
|
+
});
|
|
80
|
+
it('two shards can contribute to the same point independently', async () => {
|
|
81
|
+
registerShard({
|
|
82
|
+
manifest: { id: 'a', label: 'A', version: '0.0.0', views: [] },
|
|
83
|
+
activate(ctx) { ctx.contributions.register('shared', { from: 'a' }); },
|
|
84
|
+
});
|
|
85
|
+
registerShard({
|
|
86
|
+
manifest: { id: 'b', label: 'B', version: '0.0.0', views: [] },
|
|
87
|
+
activate(ctx) { ctx.contributions.register('shared', { from: 'b' }); },
|
|
88
|
+
});
|
|
89
|
+
await activateShard('a');
|
|
90
|
+
await activateShard('b');
|
|
91
|
+
expect(list('shared')).toEqual([{ from: 'a' }, { from: 'b' }]);
|
|
92
|
+
deactivateShard('a');
|
|
93
|
+
expect(list('shared')).toEqual([{ from: 'b' }]);
|
|
94
|
+
});
|
|
95
|
+
it('explicit unregister returned by register() is safe alongside auto-cleanup', async () => {
|
|
96
|
+
let earlyUnregister;
|
|
97
|
+
registerShard({
|
|
98
|
+
manifest: { id: 'p', label: 'P', version: '0.0.0', views: [] },
|
|
99
|
+
activate(ctx) {
|
|
100
|
+
earlyUnregister = ctx.contributions.register('p', { id: 'a' });
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
await activateShard('p');
|
|
104
|
+
expect(list('p')).toHaveLength(1);
|
|
105
|
+
earlyUnregister();
|
|
106
|
+
expect(list('p')).toEqual([]);
|
|
107
|
+
// Deactivate must not throw when the entry is already gone.
|
|
108
|
+
expect(() => deactivateShard('p')).not.toThrow();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -28,6 +28,7 @@ import { createBrowseCapability } from '../documents/browse';
|
|
|
28
28
|
import { createShardKeysApi } from '../keys/client';
|
|
29
29
|
import { PERMISSION_KEYS_MINT } from '../keys/types';
|
|
30
30
|
import { subscribe } from '../keys/revocation-bus.svelte';
|
|
31
|
+
import { register as contributionsRegister, list as contributionsList, listPoints as contributionsListPoints, onChange as contributionsOnChange, } from '../contributions';
|
|
31
32
|
/**
|
|
32
33
|
* Reactive registry of every shard known to the host. Keys are shard ids.
|
|
33
34
|
* Populated once at boot by the glob-discovery loop in main.ts (through
|
|
@@ -87,6 +88,27 @@ export async function activateShard(id) {
|
|
|
87
88
|
proxy: null,
|
|
88
89
|
defaults: null,
|
|
89
90
|
});
|
|
91
|
+
// Per-shard wrapper: every register/onChange call goes into the
|
|
92
|
+
// global registry, and its disposer is pushed into entry.cleanupFns
|
|
93
|
+
// so deactivate auto-unregisters.
|
|
94
|
+
const contributions = {
|
|
95
|
+
register(pointId, descriptor) {
|
|
96
|
+
const dispose = contributionsRegister(pointId, descriptor);
|
|
97
|
+
entry.cleanupFns.push(async () => dispose());
|
|
98
|
+
return dispose;
|
|
99
|
+
},
|
|
100
|
+
list(pointId) {
|
|
101
|
+
return contributionsList(pointId);
|
|
102
|
+
},
|
|
103
|
+
listPoints() {
|
|
104
|
+
return contributionsListPoints();
|
|
105
|
+
},
|
|
106
|
+
onChange(pointId, cb) {
|
|
107
|
+
const off = contributionsOnChange(pointId, cb);
|
|
108
|
+
entry.cleanupFns.push(async () => off());
|
|
109
|
+
return off;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
90
112
|
const ctx = {
|
|
91
113
|
state: (schema) => shell.state(id, schema),
|
|
92
114
|
registerView: (viewId, factory) => {
|
|
@@ -148,6 +170,7 @@ export async function activateShard(id) {
|
|
|
148
170
|
shardPermissions: (_d = shard.manifest.permissions) !== null && _d !== void 0 ? _d : [],
|
|
149
171
|
})
|
|
150
172
|
: undefined,
|
|
173
|
+
contributions,
|
|
151
174
|
};
|
|
152
175
|
entry.ctx = ctx;
|
|
153
176
|
// Wire onKeyRevoked hook: subscribe to the revocation bus for this shard.
|
package/dist/shards/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { BrowseCapability } from '../documents/browse';
|
|
|
5
5
|
import type { EnvState } from '../env/types';
|
|
6
6
|
import type { Verb } from '../verbs/types';
|
|
7
7
|
import type { ShardContextKeys } from '../keys/types';
|
|
8
|
+
import type { ContributionsApi } from '../contributions/types';
|
|
8
9
|
export { PERMISSION_KEYS_MINT, type ShardContextKeys, type ApiKeyPublic, type MintOpts, ScopeEscalationError, ConsentDeniedError } from '../keys/types';
|
|
9
10
|
/**
|
|
10
11
|
* The object returned by `ViewFactory.mount`. The framework calls
|
|
@@ -225,6 +226,12 @@ export interface ShardContext {
|
|
|
225
226
|
* manifest declares the `keys:mint` permission.
|
|
226
227
|
*/
|
|
227
228
|
keys?: ShardContextKeys;
|
|
229
|
+
/**
|
|
230
|
+
* Runtime registry for inter-shard contribution points. Every
|
|
231
|
+
* shard receives this — no permission gate in v1. See
|
|
232
|
+
* docs/sh3-rfcs/2026-04-20-shard-contribution-points.md.
|
|
233
|
+
*/
|
|
234
|
+
contributions: ContributionsApi;
|
|
228
235
|
}
|
|
229
236
|
/**
|
|
230
237
|
* A shard module. Shards are the fundamental unit of contribution in SH3.
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.10.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.10.1';
|