@agimon-ai/doompi-session 0.0.1-alpha.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 +21 -0
- package/README.md +44 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/api-contracts.cjs +7 -0
- package/dist/api-contracts.d.cts +2 -0
- package/dist/api-contracts.d.mts +2 -0
- package/dist/api-contracts.mjs +2 -0
- package/dist/extensions/pi.cjs +20 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +5 -0
- package/dist/extensions/pi.d.cts.map +1 -0
- package/dist/extensions/pi.d.mts +5 -0
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +15 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +37 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.cts +5 -0
- package/dist/extensions/server.d.cts.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +32 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/index.cjs +32 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +10 -0
- package/dist/src/extensions/(backend)/api/session-peer/route.server.cjs +7 -0
- package/dist/src/extensions/(backend)/api/session-peer/route.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/api/session-peer/route.server.mjs +8 -0
- package/dist/src/extensions/(backend)/api/session-peer/route.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs +10 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs +11 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +101 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +99 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -0
- package/dist/src/schemas/apiContracts.cjs +33 -0
- package/dist/src/schemas/apiContracts.cjs.map +1 -0
- package/dist/src/schemas/apiContracts.d.cts +52 -0
- package/dist/src/schemas/apiContracts.d.cts.map +1 -0
- package/dist/src/schemas/apiContracts.d.mts +52 -0
- package/dist/src/schemas/apiContracts.d.mts.map +1 -0
- package/dist/src/schemas/apiContracts.mjs +32 -0
- package/dist/src/schemas/apiContracts.mjs.map +1 -0
- package/dist/src/services/backgroundWorkService/index.cjs +79 -0
- package/dist/src/services/backgroundWorkService/index.cjs.map +1 -0
- package/dist/src/services/backgroundWorkService/index.d.cts +9 -0
- package/dist/src/services/backgroundWorkService/index.d.cts.map +1 -0
- package/dist/src/services/backgroundWorkService/index.d.mts +9 -0
- package/dist/src/services/backgroundWorkService/index.d.mts.map +1 -0
- package/dist/src/services/backgroundWorkService/index.mjs +78 -0
- package/dist/src/services/backgroundWorkService/index.mjs.map +1 -0
- package/dist/src/services/peerInbox/index.cjs +18 -0
- package/dist/src/services/peerInbox/index.cjs.map +1 -0
- package/dist/src/services/peerInbox/index.mjs +17 -0
- package/dist/src/services/peerInbox/index.mjs.map +1 -0
- package/dist/src/services/peerInboxApi/index.cjs +48 -0
- package/dist/src/services/peerInboxApi/index.cjs.map +1 -0
- package/dist/src/services/peerInboxApi/index.mjs +48 -0
- package/dist/src/services/peerInboxApi/index.mjs.map +1 -0
- package/dist/src/services/peerTransport/index.cjs +206 -0
- package/dist/src/services/peerTransport/index.cjs.map +1 -0
- package/dist/src/services/peerTransport/index.d.cts +49 -0
- package/dist/src/services/peerTransport/index.d.cts.map +1 -0
- package/dist/src/services/peerTransport/index.d.mts +49 -0
- package/dist/src/services/peerTransport/index.d.mts.map +1 -0
- package/dist/src/services/peerTransport/index.mjs +195 -0
- package/dist/src/services/peerTransport/index.mjs.map +1 -0
- package/dist/src/services/sessionDelivery/index.cjs +277 -0
- package/dist/src/services/sessionDelivery/index.cjs.map +1 -0
- package/dist/src/services/sessionDelivery/index.d.cts +10 -0
- package/dist/src/services/sessionDelivery/index.d.cts.map +1 -0
- package/dist/src/services/sessionDelivery/index.d.mts +10 -0
- package/dist/src/services/sessionDelivery/index.d.mts.map +1 -0
- package/dist/src/services/sessionDelivery/index.mjs +274 -0
- package/dist/src/services/sessionDelivery/index.mjs.map +1 -0
- package/dist/src/services/sessionDelivery/type.cjs +11 -0
- package/dist/src/services/sessionDelivery/type.cjs.map +1 -0
- package/dist/src/services/sessionDelivery/type.d.cts +73 -0
- package/dist/src/services/sessionDelivery/type.d.cts.map +1 -0
- package/dist/src/services/sessionDelivery/type.d.mts +73 -0
- package/dist/src/services/sessionDelivery/type.d.mts.map +1 -0
- package/dist/src/services/sessionDelivery/type.mjs +10 -0
- package/dist/src/services/sessionDelivery/type.mjs.map +1 -0
- package/dist/src/services/sessionDirectory/index.cjs +120 -0
- package/dist/src/services/sessionDirectory/index.cjs.map +1 -0
- package/dist/src/services/sessionDirectory/index.d.cts +7 -0
- package/dist/src/services/sessionDirectory/index.d.cts.map +1 -0
- package/dist/src/services/sessionDirectory/index.d.mts +7 -0
- package/dist/src/services/sessionDirectory/index.d.mts.map +1 -0
- package/dist/src/services/sessionDirectory/index.mjs +120 -0
- package/dist/src/services/sessionDirectory/index.mjs.map +1 -0
- package/dist/src/services/sessionDirectory/type.d.cts +54 -0
- package/dist/src/services/sessionDirectory/type.d.cts.map +1 -0
- package/dist/src/services/sessionDirectory/type.d.mts +54 -0
- package/dist/src/services/sessionDirectory/type.d.mts.map +1 -0
- package/dist/src/services/sessionGroups/index.cjs +105 -0
- package/dist/src/services/sessionGroups/index.cjs.map +1 -0
- package/dist/src/services/sessionGroups/index.d.cts +7 -0
- package/dist/src/services/sessionGroups/index.d.cts.map +1 -0
- package/dist/src/services/sessionGroups/index.d.mts +7 -0
- package/dist/src/services/sessionGroups/index.d.mts.map +1 -0
- package/dist/src/services/sessionGroups/index.mjs +104 -0
- package/dist/src/services/sessionGroups/index.mjs.map +1 -0
- package/dist/src/services/sessionGroups/type.cjs +11 -0
- package/dist/src/services/sessionGroups/type.cjs.map +1 -0
- package/dist/src/services/sessionGroups/type.d.cts +32 -0
- package/dist/src/services/sessionGroups/type.d.cts.map +1 -0
- package/dist/src/services/sessionGroups/type.d.mts +32 -0
- package/dist/src/services/sessionGroups/type.d.mts.map +1 -0
- package/dist/src/services/sessionGroups/type.mjs +11 -0
- package/dist/src/services/sessionGroups/type.mjs.map +1 -0
- package/dist/src/services/sessionIntercom/index.cjs +89 -0
- package/dist/src/services/sessionIntercom/index.cjs.map +1 -0
- package/dist/src/services/sessionIntercom/index.d.cts +11 -0
- package/dist/src/services/sessionIntercom/index.d.cts.map +1 -0
- package/dist/src/services/sessionIntercom/index.d.mts +11 -0
- package/dist/src/services/sessionIntercom/index.d.mts.map +1 -0
- package/dist/src/services/sessionIntercom/index.mjs +85 -0
- package/dist/src/services/sessionIntercom/index.mjs.map +1 -0
- package/dist/src/services/sessionIntercom/type.cjs +16 -0
- package/dist/src/services/sessionIntercom/type.cjs.map +1 -0
- package/dist/src/services/sessionIntercom/type.d.cts +60 -0
- package/dist/src/services/sessionIntercom/type.d.cts.map +1 -0
- package/dist/src/services/sessionIntercom/type.d.mts +60 -0
- package/dist/src/services/sessionIntercom/type.d.mts.map +1 -0
- package/dist/src/services/sessionIntercom/type.mjs +14 -0
- package/dist/src/services/sessionIntercom/type.mjs.map +1 -0
- package/llms.txt +3 -0
- package/package.json +115 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/services/sessionDirectory/type.d.ts
|
|
2
|
+
export type SessionHostReachability = 'reachable' | 'unreachable' | 'unknown';
|
|
3
|
+
export type SessionRuntimeState = 'starting' | 'ready' | 'dormant' | 'closing' | 'closed' | 'unknown';
|
|
4
|
+
export type SessionActivityState = 'busy' | 'awaiting_input' | 'fully_idle' | 'unknown';
|
|
5
|
+
export type SessionVoiceReadiness = 'ready' | 'not_ready' | 'unknown';
|
|
6
|
+
export interface SessionPresenceFacts {
|
|
7
|
+
readonly reachability: SessionHostReachability;
|
|
8
|
+
readonly runtime: SessionRuntimeState;
|
|
9
|
+
readonly activity: SessionActivityState;
|
|
10
|
+
readonly voice: {
|
|
11
|
+
readonly eligible: boolean | 'unknown';
|
|
12
|
+
readonly readiness: SessionVoiceReadiness;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface SessionPresenceObservation extends SessionPresenceFacts {
|
|
16
|
+
readonly hostId: string;
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
readonly hostIncarnation: string;
|
|
19
|
+
readonly sessionIncarnation: string;
|
|
20
|
+
readonly sequence: number;
|
|
21
|
+
readonly observedAt: number;
|
|
22
|
+
readonly staleAfterMs: number;
|
|
23
|
+
readonly label?: string;
|
|
24
|
+
readonly deliveryTarget: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SessionDiscoveryGrant {
|
|
27
|
+
readonly label?: string;
|
|
28
|
+
readonly capabilities: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
export interface DiscoveredSession extends SessionPresenceFacts {
|
|
31
|
+
readonly reference: string;
|
|
32
|
+
readonly label?: string;
|
|
33
|
+
readonly capabilities: readonly string[];
|
|
34
|
+
readonly hostIncarnation: string;
|
|
35
|
+
readonly sessionIncarnation: string;
|
|
36
|
+
readonly sequence: number;
|
|
37
|
+
readonly observedAt: number;
|
|
38
|
+
readonly freshUntil: number;
|
|
39
|
+
readonly fresh: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface SessionDirectoryOptions {
|
|
42
|
+
readonly now?: () => number;
|
|
43
|
+
readonly authorizeDiscovery: (callerReference: string, subject: Readonly<Pick<SessionPresenceObservation, 'hostId' | 'sessionId' | 'label'>>) => SessionDiscoveryGrant | undefined;
|
|
44
|
+
}
|
|
45
|
+
export interface SessionDirectory {
|
|
46
|
+
observe(observation: SessionPresenceObservation): boolean;
|
|
47
|
+
replaceHostSnapshot(hostId: string, hostIncarnation: string, observations: readonly SessionPresenceObservation[]): void;
|
|
48
|
+
hostDisconnected(hostId: string): void;
|
|
49
|
+
discover(callerReference: string, allowedReferences?: ReadonlySet<string>): readonly DiscoveredSession[];
|
|
50
|
+
resolveDeliveryTarget(callerReference: string, reference: string): string | undefined;
|
|
51
|
+
subscribe(callerReference: string, listener: (sessions: readonly DiscoveredSession[]) => void): () => void;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//# sourceMappingURL=type.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.d.mts","names":[],"sources":["../../../../src/services/sessionDirectory/type.ts"],"mappings":";YAAY;YACA;YACA;YACA;iBAEK;WACN,cAAc;WACd,SAAS;WACT,UAAU;WACV;aACE;aACA,WAAW;;;iBAIP,mCAAmC;WACzC;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;iBAGM;WACN;WACA;;iBAGM,0BAA0B;WAChC;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;iBAGM;WACN;WACA,qBACP,yBACA,SAAS,SAAS,KAAK,mEACpB;;iBAGU;EACf,QAAQ,aAAa;EACrB,oBACE,gBACA,yBACA,uBAAuB;EAEzB,iBAAiB;EACjB,SAAS,yBAAyB,oBAAoB,+BAA+B;EACrF,sBAAsB,yBAAyB;EAC/C,UAAU,yBAAyB,WAAW,mBAAmB"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const require_type = require("./type.cjs");
|
|
2
|
+
let node_sqlite = require("node:sqlite");
|
|
3
|
+
//#region src/services/sessionGroups/index.ts
|
|
4
|
+
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
5
|
+
const REFERENCE_PATTERN = /^peer\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
6
|
+
function validateGroupId(value) {
|
|
7
|
+
if (!ID_PATTERN.test(value)) throw new TypeError("Session group IDs must be safe opaque IDs.");
|
|
8
|
+
}
|
|
9
|
+
function validateReference(value) {
|
|
10
|
+
if (!REFERENCE_PATTERN.test(value)) throw new TypeError(`Session group member '${value}' is not a qualified reference.`);
|
|
11
|
+
}
|
|
12
|
+
/** Opens a revisioned SQLite registry for communication-only group membership. */
|
|
13
|
+
function createSessionGroupStore(options) {
|
|
14
|
+
const database = new node_sqlite.DatabaseSync(options.databasePath);
|
|
15
|
+
database.exec(`
|
|
16
|
+
PRAGMA journal_mode = WAL;
|
|
17
|
+
PRAGMA foreign_keys = ON;
|
|
18
|
+
CREATE TABLE IF NOT EXISTS session_groups (
|
|
19
|
+
group_id TEXT PRIMARY KEY,
|
|
20
|
+
owner_authority TEXT NOT NULL,
|
|
21
|
+
revision INTEGER NOT NULL
|
|
22
|
+
);
|
|
23
|
+
CREATE TABLE IF NOT EXISTS session_group_members (
|
|
24
|
+
group_id TEXT NOT NULL REFERENCES session_groups(group_id) ON DELETE CASCADE,
|
|
25
|
+
member_reference TEXT NOT NULL,
|
|
26
|
+
PRIMARY KEY (group_id, member_reference)
|
|
27
|
+
);
|
|
28
|
+
`);
|
|
29
|
+
const read = (groupId) => {
|
|
30
|
+
const row = database.prepare("SELECT * FROM session_groups WHERE group_id = ?").get(groupId);
|
|
31
|
+
if (!row) return void 0;
|
|
32
|
+
const members = database.prepare("SELECT member_reference FROM session_group_members WHERE group_id = ? ORDER BY member_reference").all(groupId);
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
id: row.group_id,
|
|
35
|
+
ownerAuthority: row.owner_authority,
|
|
36
|
+
revision: row.revision,
|
|
37
|
+
members: Object.freeze(members.map((member) => member.member_reference))
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const enroll = (groupId, members) => {
|
|
41
|
+
const insert = database.prepare("INSERT OR IGNORE INTO session_group_members (group_id, member_reference) VALUES (?, ?)");
|
|
42
|
+
for (const member of new Set(members)) {
|
|
43
|
+
validateReference(member);
|
|
44
|
+
if (options.validateEnrollment?.(groupId, member) === false) throw new Error(`Session group enrollment policy rejected '${member}'.`);
|
|
45
|
+
insert.run(groupId, member);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
create(groupId, ownerAuthority, members) {
|
|
50
|
+
validateGroupId(groupId);
|
|
51
|
+
if (!ownerAuthority.trim()) throw new TypeError("Session group owner authority is required.");
|
|
52
|
+
database.exec("BEGIN IMMEDIATE");
|
|
53
|
+
try {
|
|
54
|
+
database.prepare("INSERT INTO session_groups (group_id, owner_authority, revision) VALUES (?, ?, 1)").run(groupId, ownerAuthority);
|
|
55
|
+
enroll(groupId, members);
|
|
56
|
+
database.exec("COMMIT");
|
|
57
|
+
} catch (cause) {
|
|
58
|
+
database.exec("ROLLBACK");
|
|
59
|
+
throw cause;
|
|
60
|
+
}
|
|
61
|
+
return read(groupId);
|
|
62
|
+
},
|
|
63
|
+
mutate(mutation) {
|
|
64
|
+
database.exec("BEGIN IMMEDIATE");
|
|
65
|
+
try {
|
|
66
|
+
const current = read(mutation.groupId);
|
|
67
|
+
if (!current) throw new Error(`Session group '${mutation.groupId}' does not exist.`);
|
|
68
|
+
if (mutation.authority !== current.ownerAuthority && options.authorizeManagement?.(mutation.authority, current) !== true) throw new Error(`Authority '${mutation.authority}' cannot manage Session group '${mutation.groupId}'.`);
|
|
69
|
+
if (mutation.expectedRevision !== current.revision) throw new require_type.SessionGroupRevisionError(mutation.groupId, mutation.expectedRevision, current.revision);
|
|
70
|
+
enroll(mutation.groupId, mutation.add ?? []);
|
|
71
|
+
const remove = database.prepare("DELETE FROM session_group_members WHERE group_id = ? AND member_reference = ?");
|
|
72
|
+
for (const member of new Set(mutation.remove ?? [])) {
|
|
73
|
+
validateReference(member);
|
|
74
|
+
remove.run(mutation.groupId, member);
|
|
75
|
+
}
|
|
76
|
+
database.prepare("UPDATE session_groups SET revision = revision + 1 WHERE group_id = ? AND revision = ?").run(mutation.groupId, current.revision);
|
|
77
|
+
database.exec("COMMIT");
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
database.exec("ROLLBACK");
|
|
80
|
+
throw cause;
|
|
81
|
+
}
|
|
82
|
+
return read(mutation.groupId);
|
|
83
|
+
},
|
|
84
|
+
get: read,
|
|
85
|
+
groupsFor(memberReference) {
|
|
86
|
+
validateReference(memberReference);
|
|
87
|
+
return database.prepare(`SELECT g.group_id FROM session_groups g
|
|
88
|
+
INNER JOIN session_group_members m ON m.group_id = g.group_id
|
|
89
|
+
WHERE m.member_reference = ? ORDER BY g.group_id`).all(memberReference).map((row) => read(row.group_id));
|
|
90
|
+
},
|
|
91
|
+
canMessage(groupId, sourceReference, targetReference) {
|
|
92
|
+
validateReference(sourceReference);
|
|
93
|
+
validateReference(targetReference);
|
|
94
|
+
const row = database.prepare(`SELECT COUNT(*) AS count FROM session_group_members
|
|
95
|
+
WHERE group_id = ? AND member_reference IN (?, ?)`).get(groupId, sourceReference, targetReference);
|
|
96
|
+
return Number(row.count) === (sourceReference === targetReference ? 1 : 2);
|
|
97
|
+
},
|
|
98
|
+
close: () => database.close()
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
exports.SessionGroupRevisionError = require_type.SessionGroupRevisionError;
|
|
103
|
+
exports.createSessionGroupStore = createSessionGroupStore;
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["DatabaseSync","SessionGroupRevisionError"],"sources":["../../../../src/services/sessionGroups/index.ts"],"sourcesContent":["import { DatabaseSync } from 'node:sqlite';\n\nimport {\n SessionGroupRevisionError,\n type SessionCommunicationGroup,\n type SessionGroupMutation,\n type SessionGroupStore,\n type SessionGroupStoreOptions,\n} from './type';\n\ninterface GroupRow {\n group_id: string;\n owner_authority: string;\n revision: number;\n}\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;\nconst REFERENCE_PATTERN = /^peer\\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;\n\nfunction validateGroupId(value: string): void {\n if (!ID_PATTERN.test(value)) throw new TypeError('Session group IDs must be safe opaque IDs.');\n}\n\nfunction validateReference(value: string): void {\n if (!REFERENCE_PATTERN.test(value))\n throw new TypeError(`Session group member '${value}' is not a qualified reference.`);\n}\n\n/** Opens a revisioned SQLite registry for communication-only group membership. */\nexport function createSessionGroupStore(options: SessionGroupStoreOptions): SessionGroupStore {\n const database = new DatabaseSync(options.databasePath);\n database.exec(`\n PRAGMA journal_mode = WAL;\n PRAGMA foreign_keys = ON;\n CREATE TABLE IF NOT EXISTS session_groups (\n group_id TEXT PRIMARY KEY,\n owner_authority TEXT NOT NULL,\n revision INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS session_group_members (\n group_id TEXT NOT NULL REFERENCES session_groups(group_id) ON DELETE CASCADE,\n member_reference TEXT NOT NULL,\n PRIMARY KEY (group_id, member_reference)\n );\n `);\n\n const read = (groupId: string): SessionCommunicationGroup | undefined => {\n const row = database.prepare('SELECT * FROM session_groups WHERE group_id = ?').get(groupId) as\n | GroupRow\n | undefined;\n if (!row) return undefined;\n const members = database\n .prepare('SELECT member_reference FROM session_group_members WHERE group_id = ? ORDER BY member_reference')\n .all(groupId) as { member_reference: string }[];\n return Object.freeze({\n id: row.group_id,\n ownerAuthority: row.owner_authority,\n revision: row.revision,\n members: Object.freeze(members.map((member) => member.member_reference)),\n });\n };\n\n const enroll = (groupId: string, members: readonly string[]): void => {\n const insert = database.prepare(\n 'INSERT OR IGNORE INTO session_group_members (group_id, member_reference) VALUES (?, ?)',\n );\n for (const member of new Set(members)) {\n validateReference(member);\n if (options.validateEnrollment?.(groupId, member) === false)\n throw new Error(`Session group enrollment policy rejected '${member}'.`);\n insert.run(groupId, member);\n }\n };\n\n return {\n create(groupId, ownerAuthority, members) {\n validateGroupId(groupId);\n if (!ownerAuthority.trim()) throw new TypeError('Session group owner authority is required.');\n database.exec('BEGIN IMMEDIATE');\n try {\n database\n .prepare('INSERT INTO session_groups (group_id, owner_authority, revision) VALUES (?, ?, 1)')\n .run(groupId, ownerAuthority);\n enroll(groupId, members);\n database.exec('COMMIT');\n } catch (cause) {\n database.exec('ROLLBACK');\n throw cause;\n }\n return read(groupId)!;\n },\n mutate(mutation: SessionGroupMutation) {\n database.exec('BEGIN IMMEDIATE');\n try {\n const current = read(mutation.groupId);\n if (!current) throw new Error(`Session group '${mutation.groupId}' does not exist.`);\n if (\n mutation.authority !== current.ownerAuthority &&\n options.authorizeManagement?.(mutation.authority, current) !== true\n ) {\n throw new Error(`Authority '${mutation.authority}' cannot manage Session group '${mutation.groupId}'.`);\n }\n if (mutation.expectedRevision !== current.revision)\n throw new SessionGroupRevisionError(mutation.groupId, mutation.expectedRevision, current.revision);\n enroll(mutation.groupId, mutation.add ?? []);\n const remove = database.prepare(\n 'DELETE FROM session_group_members WHERE group_id = ? AND member_reference = ?',\n );\n for (const member of new Set(mutation.remove ?? [])) {\n validateReference(member);\n remove.run(mutation.groupId, member);\n }\n database\n .prepare('UPDATE session_groups SET revision = revision + 1 WHERE group_id = ? AND revision = ?')\n .run(mutation.groupId, current.revision);\n database.exec('COMMIT');\n } catch (cause) {\n database.exec('ROLLBACK');\n throw cause;\n }\n return read(mutation.groupId)!;\n },\n get: read,\n groupsFor(memberReference) {\n validateReference(memberReference);\n const rows = database\n .prepare(\n `SELECT g.group_id FROM session_groups g\n INNER JOIN session_group_members m ON m.group_id = g.group_id\n WHERE m.member_reference = ? ORDER BY g.group_id`,\n )\n .all(memberReference) as { group_id: string }[];\n return rows.map((row) => read(row.group_id)!);\n },\n canMessage(groupId, sourceReference, targetReference) {\n validateReference(sourceReference);\n validateReference(targetReference);\n const row = database\n .prepare(\n `SELECT COUNT(*) AS count FROM session_group_members\n WHERE group_id = ? AND member_reference IN (?, ?)`,\n )\n .get(groupId, sourceReference, targetReference) as { count: number | bigint };\n return Number(row.count) === (sourceReference === targetReference ? 1 : 2);\n },\n close: () => database.close(),\n };\n}\n\nexport {\n SessionGroupRevisionError,\n type SessionCommunicationGroup,\n type SessionGroupMutation,\n type SessionGroupStore,\n type SessionGroupStoreOptions,\n} from './type';\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SessionCommunicationGroup, SessionGroupMutation, SessionGroupRevisionError, SessionGroupStore, SessionGroupStoreOptions } from "./type.cjs";
|
|
2
|
+
//#region src/services/sessionGroups/index.d.ts
|
|
3
|
+
/** Opens a revisioned SQLite registry for communication-only group membership. */
|
|
4
|
+
export declare function createSessionGroupStore(options: SessionGroupStoreOptions): SessionGroupStore;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { type SessionCommunicationGroup, type SessionGroupMutation, SessionGroupRevisionError, type SessionGroupStore, type SessionGroupStoreOptions };
|
|
7
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/sessionGroups/index.ts"],"mappings":";;;wBA6BgB,wBAAwB,SAAS,2BAA2B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SessionCommunicationGroup, SessionGroupMutation, SessionGroupRevisionError, SessionGroupStore, SessionGroupStoreOptions } from "./type.mjs";
|
|
2
|
+
//#region src/services/sessionGroups/index.d.ts
|
|
3
|
+
/** Opens a revisioned SQLite registry for communication-only group membership. */
|
|
4
|
+
export declare function createSessionGroupStore(options: SessionGroupStoreOptions): SessionGroupStore;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { type SessionCommunicationGroup, type SessionGroupMutation, SessionGroupRevisionError, type SessionGroupStore, type SessionGroupStoreOptions };
|
|
7
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/sessionGroups/index.ts"],"mappings":";;;wBA6BgB,wBAAwB,SAAS,2BAA2B"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SessionGroupRevisionError } from "./type.mjs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
//#region src/services/sessionGroups/index.ts
|
|
4
|
+
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
5
|
+
const REFERENCE_PATTERN = /^peer\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
6
|
+
function validateGroupId(value) {
|
|
7
|
+
if (!ID_PATTERN.test(value)) throw new TypeError("Session group IDs must be safe opaque IDs.");
|
|
8
|
+
}
|
|
9
|
+
function validateReference(value) {
|
|
10
|
+
if (!REFERENCE_PATTERN.test(value)) throw new TypeError(`Session group member '${value}' is not a qualified reference.`);
|
|
11
|
+
}
|
|
12
|
+
/** Opens a revisioned SQLite registry for communication-only group membership. */
|
|
13
|
+
function createSessionGroupStore(options) {
|
|
14
|
+
const database = new DatabaseSync(options.databasePath);
|
|
15
|
+
database.exec(`
|
|
16
|
+
PRAGMA journal_mode = WAL;
|
|
17
|
+
PRAGMA foreign_keys = ON;
|
|
18
|
+
CREATE TABLE IF NOT EXISTS session_groups (
|
|
19
|
+
group_id TEXT PRIMARY KEY,
|
|
20
|
+
owner_authority TEXT NOT NULL,
|
|
21
|
+
revision INTEGER NOT NULL
|
|
22
|
+
);
|
|
23
|
+
CREATE TABLE IF NOT EXISTS session_group_members (
|
|
24
|
+
group_id TEXT NOT NULL REFERENCES session_groups(group_id) ON DELETE CASCADE,
|
|
25
|
+
member_reference TEXT NOT NULL,
|
|
26
|
+
PRIMARY KEY (group_id, member_reference)
|
|
27
|
+
);
|
|
28
|
+
`);
|
|
29
|
+
const read = (groupId) => {
|
|
30
|
+
const row = database.prepare("SELECT * FROM session_groups WHERE group_id = ?").get(groupId);
|
|
31
|
+
if (!row) return void 0;
|
|
32
|
+
const members = database.prepare("SELECT member_reference FROM session_group_members WHERE group_id = ? ORDER BY member_reference").all(groupId);
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
id: row.group_id,
|
|
35
|
+
ownerAuthority: row.owner_authority,
|
|
36
|
+
revision: row.revision,
|
|
37
|
+
members: Object.freeze(members.map((member) => member.member_reference))
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const enroll = (groupId, members) => {
|
|
41
|
+
const insert = database.prepare("INSERT OR IGNORE INTO session_group_members (group_id, member_reference) VALUES (?, ?)");
|
|
42
|
+
for (const member of new Set(members)) {
|
|
43
|
+
validateReference(member);
|
|
44
|
+
if (options.validateEnrollment?.(groupId, member) === false) throw new Error(`Session group enrollment policy rejected '${member}'.`);
|
|
45
|
+
insert.run(groupId, member);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
create(groupId, ownerAuthority, members) {
|
|
50
|
+
validateGroupId(groupId);
|
|
51
|
+
if (!ownerAuthority.trim()) throw new TypeError("Session group owner authority is required.");
|
|
52
|
+
database.exec("BEGIN IMMEDIATE");
|
|
53
|
+
try {
|
|
54
|
+
database.prepare("INSERT INTO session_groups (group_id, owner_authority, revision) VALUES (?, ?, 1)").run(groupId, ownerAuthority);
|
|
55
|
+
enroll(groupId, members);
|
|
56
|
+
database.exec("COMMIT");
|
|
57
|
+
} catch (cause) {
|
|
58
|
+
database.exec("ROLLBACK");
|
|
59
|
+
throw cause;
|
|
60
|
+
}
|
|
61
|
+
return read(groupId);
|
|
62
|
+
},
|
|
63
|
+
mutate(mutation) {
|
|
64
|
+
database.exec("BEGIN IMMEDIATE");
|
|
65
|
+
try {
|
|
66
|
+
const current = read(mutation.groupId);
|
|
67
|
+
if (!current) throw new Error(`Session group '${mutation.groupId}' does not exist.`);
|
|
68
|
+
if (mutation.authority !== current.ownerAuthority && options.authorizeManagement?.(mutation.authority, current) !== true) throw new Error(`Authority '${mutation.authority}' cannot manage Session group '${mutation.groupId}'.`);
|
|
69
|
+
if (mutation.expectedRevision !== current.revision) throw new SessionGroupRevisionError(mutation.groupId, mutation.expectedRevision, current.revision);
|
|
70
|
+
enroll(mutation.groupId, mutation.add ?? []);
|
|
71
|
+
const remove = database.prepare("DELETE FROM session_group_members WHERE group_id = ? AND member_reference = ?");
|
|
72
|
+
for (const member of new Set(mutation.remove ?? [])) {
|
|
73
|
+
validateReference(member);
|
|
74
|
+
remove.run(mutation.groupId, member);
|
|
75
|
+
}
|
|
76
|
+
database.prepare("UPDATE session_groups SET revision = revision + 1 WHERE group_id = ? AND revision = ?").run(mutation.groupId, current.revision);
|
|
77
|
+
database.exec("COMMIT");
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
database.exec("ROLLBACK");
|
|
80
|
+
throw cause;
|
|
81
|
+
}
|
|
82
|
+
return read(mutation.groupId);
|
|
83
|
+
},
|
|
84
|
+
get: read,
|
|
85
|
+
groupsFor(memberReference) {
|
|
86
|
+
validateReference(memberReference);
|
|
87
|
+
return database.prepare(`SELECT g.group_id FROM session_groups g
|
|
88
|
+
INNER JOIN session_group_members m ON m.group_id = g.group_id
|
|
89
|
+
WHERE m.member_reference = ? ORDER BY g.group_id`).all(memberReference).map((row) => read(row.group_id));
|
|
90
|
+
},
|
|
91
|
+
canMessage(groupId, sourceReference, targetReference) {
|
|
92
|
+
validateReference(sourceReference);
|
|
93
|
+
validateReference(targetReference);
|
|
94
|
+
const row = database.prepare(`SELECT COUNT(*) AS count FROM session_group_members
|
|
95
|
+
WHERE group_id = ? AND member_reference IN (?, ?)`).get(groupId, sourceReference, targetReference);
|
|
96
|
+
return Number(row.count) === (sourceReference === targetReference ? 1 : 2);
|
|
97
|
+
},
|
|
98
|
+
close: () => database.close()
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { SessionGroupRevisionError, createSessionGroupStore };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/sessionGroups/index.ts"],"sourcesContent":["import { DatabaseSync } from 'node:sqlite';\n\nimport {\n SessionGroupRevisionError,\n type SessionCommunicationGroup,\n type SessionGroupMutation,\n type SessionGroupStore,\n type SessionGroupStoreOptions,\n} from './type';\n\ninterface GroupRow {\n group_id: string;\n owner_authority: string;\n revision: number;\n}\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;\nconst REFERENCE_PATTERN = /^peer\\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;\n\nfunction validateGroupId(value: string): void {\n if (!ID_PATTERN.test(value)) throw new TypeError('Session group IDs must be safe opaque IDs.');\n}\n\nfunction validateReference(value: string): void {\n if (!REFERENCE_PATTERN.test(value))\n throw new TypeError(`Session group member '${value}' is not a qualified reference.`);\n}\n\n/** Opens a revisioned SQLite registry for communication-only group membership. */\nexport function createSessionGroupStore(options: SessionGroupStoreOptions): SessionGroupStore {\n const database = new DatabaseSync(options.databasePath);\n database.exec(`\n PRAGMA journal_mode = WAL;\n PRAGMA foreign_keys = ON;\n CREATE TABLE IF NOT EXISTS session_groups (\n group_id TEXT PRIMARY KEY,\n owner_authority TEXT NOT NULL,\n revision INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS session_group_members (\n group_id TEXT NOT NULL REFERENCES session_groups(group_id) ON DELETE CASCADE,\n member_reference TEXT NOT NULL,\n PRIMARY KEY (group_id, member_reference)\n );\n `);\n\n const read = (groupId: string): SessionCommunicationGroup | undefined => {\n const row = database.prepare('SELECT * FROM session_groups WHERE group_id = ?').get(groupId) as\n | GroupRow\n | undefined;\n if (!row) return undefined;\n const members = database\n .prepare('SELECT member_reference FROM session_group_members WHERE group_id = ? ORDER BY member_reference')\n .all(groupId) as { member_reference: string }[];\n return Object.freeze({\n id: row.group_id,\n ownerAuthority: row.owner_authority,\n revision: row.revision,\n members: Object.freeze(members.map((member) => member.member_reference)),\n });\n };\n\n const enroll = (groupId: string, members: readonly string[]): void => {\n const insert = database.prepare(\n 'INSERT OR IGNORE INTO session_group_members (group_id, member_reference) VALUES (?, ?)',\n );\n for (const member of new Set(members)) {\n validateReference(member);\n if (options.validateEnrollment?.(groupId, member) === false)\n throw new Error(`Session group enrollment policy rejected '${member}'.`);\n insert.run(groupId, member);\n }\n };\n\n return {\n create(groupId, ownerAuthority, members) {\n validateGroupId(groupId);\n if (!ownerAuthority.trim()) throw new TypeError('Session group owner authority is required.');\n database.exec('BEGIN IMMEDIATE');\n try {\n database\n .prepare('INSERT INTO session_groups (group_id, owner_authority, revision) VALUES (?, ?, 1)')\n .run(groupId, ownerAuthority);\n enroll(groupId, members);\n database.exec('COMMIT');\n } catch (cause) {\n database.exec('ROLLBACK');\n throw cause;\n }\n return read(groupId)!;\n },\n mutate(mutation: SessionGroupMutation) {\n database.exec('BEGIN IMMEDIATE');\n try {\n const current = read(mutation.groupId);\n if (!current) throw new Error(`Session group '${mutation.groupId}' does not exist.`);\n if (\n mutation.authority !== current.ownerAuthority &&\n options.authorizeManagement?.(mutation.authority, current) !== true\n ) {\n throw new Error(`Authority '${mutation.authority}' cannot manage Session group '${mutation.groupId}'.`);\n }\n if (mutation.expectedRevision !== current.revision)\n throw new SessionGroupRevisionError(mutation.groupId, mutation.expectedRevision, current.revision);\n enroll(mutation.groupId, mutation.add ?? []);\n const remove = database.prepare(\n 'DELETE FROM session_group_members WHERE group_id = ? AND member_reference = ?',\n );\n for (const member of new Set(mutation.remove ?? [])) {\n validateReference(member);\n remove.run(mutation.groupId, member);\n }\n database\n .prepare('UPDATE session_groups SET revision = revision + 1 WHERE group_id = ? AND revision = ?')\n .run(mutation.groupId, current.revision);\n database.exec('COMMIT');\n } catch (cause) {\n database.exec('ROLLBACK');\n throw cause;\n }\n return read(mutation.groupId)!;\n },\n get: read,\n groupsFor(memberReference) {\n validateReference(memberReference);\n const rows = database\n .prepare(\n `SELECT g.group_id FROM session_groups g\n INNER JOIN session_group_members m ON m.group_id = g.group_id\n WHERE m.member_reference = ? ORDER BY g.group_id`,\n )\n .all(memberReference) as { group_id: string }[];\n return rows.map((row) => read(row.group_id)!);\n },\n canMessage(groupId, sourceReference, targetReference) {\n validateReference(sourceReference);\n validateReference(targetReference);\n const row = database\n .prepare(\n `SELECT COUNT(*) AS count FROM session_group_members\n WHERE group_id = ? AND member_reference IN (?, ?)`,\n )\n .get(groupId, sourceReference, targetReference) as { count: number | bigint };\n return Number(row.count) === (sourceReference === targetReference ? 1 : 2);\n },\n close: () => database.close(),\n };\n}\n\nexport {\n SessionGroupRevisionError,\n type SessionCommunicationGroup,\n type SessionGroupMutation,\n type SessionGroupStore,\n type SessionGroupStoreOptions,\n} from './type';\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/services/sessionGroups/type.ts
|
|
2
|
+
var SessionGroupRevisionError = class extends Error {
|
|
3
|
+
constructor(groupId, expected, actual) {
|
|
4
|
+
super(`Session group '${groupId}' revision conflict: expected ${expected}, current ${actual}.`);
|
|
5
|
+
this.name = "SessionGroupRevisionError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
exports.SessionGroupRevisionError = SessionGroupRevisionError;
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=type.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.cjs","names":[],"sources":["../../../../src/services/sessionGroups/type.ts"],"sourcesContent":["export interface SessionCommunicationGroup {\n readonly id: string;\n readonly ownerAuthority: string;\n readonly revision: number;\n readonly members: readonly string[];\n}\n\nexport interface SessionGroupMutation {\n readonly groupId: string;\n readonly authority: string;\n readonly expectedRevision: number;\n readonly add?: readonly string[];\n readonly remove?: readonly string[];\n}\n\nexport interface SessionGroupStoreOptions {\n readonly databasePath: string;\n readonly authorizeManagement?: (authority: string, group: SessionCommunicationGroup) => boolean;\n readonly validateEnrollment?: (groupId: string, memberReference: string) => boolean;\n}\n\nexport interface SessionGroupStore {\n create(groupId: string, ownerAuthority: string, members: readonly string[]): SessionCommunicationGroup;\n mutate(mutation: SessionGroupMutation): SessionCommunicationGroup;\n get(groupId: string): SessionCommunicationGroup | undefined;\n groupsFor(memberReference: string): readonly SessionCommunicationGroup[];\n canMessage(groupId: string, sourceReference: string, targetReference: string): boolean;\n close(): void;\n}\n\nexport class SessionGroupRevisionError extends Error {\n constructor(groupId: string, expected: number, actual: number) {\n super(`Session group '${groupId}' revision conflict: expected ${expected}, current ${actual}.`);\n this.name = 'SessionGroupRevisionError';\n }\n}\n"],"mappings":";AA8BA,IAAa,4BAAb,cAA+C,MAAM;CACnD,YAAY,SAAiB,UAAkB,QAAgB;EAC7D,MAAM,kBAAkB,QAAQ,gCAAgC,SAAS,YAAY,OAAO,EAAE;EAC9F,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/services/sessionGroups/type.d.ts
|
|
2
|
+
export interface SessionCommunicationGroup {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly ownerAuthority: string;
|
|
5
|
+
readonly revision: number;
|
|
6
|
+
readonly members: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface SessionGroupMutation {
|
|
9
|
+
readonly groupId: string;
|
|
10
|
+
readonly authority: string;
|
|
11
|
+
readonly expectedRevision: number;
|
|
12
|
+
readonly add?: readonly string[];
|
|
13
|
+
readonly remove?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export interface SessionGroupStoreOptions {
|
|
16
|
+
readonly databasePath: string;
|
|
17
|
+
readonly authorizeManagement?: (authority: string, group: SessionCommunicationGroup) => boolean;
|
|
18
|
+
readonly validateEnrollment?: (groupId: string, memberReference: string) => boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface SessionGroupStore {
|
|
21
|
+
create(groupId: string, ownerAuthority: string, members: readonly string[]): SessionCommunicationGroup;
|
|
22
|
+
mutate(mutation: SessionGroupMutation): SessionCommunicationGroup;
|
|
23
|
+
get(groupId: string): SessionCommunicationGroup | undefined;
|
|
24
|
+
groupsFor(memberReference: string): readonly SessionCommunicationGroup[];
|
|
25
|
+
canMessage(groupId: string, sourceReference: string, targetReference: string): boolean;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare class SessionGroupRevisionError extends Error {
|
|
29
|
+
constructor(groupId: string, expected: number, actual: number);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=type.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.d.cts","names":[],"sources":["../../../../src/services/sessionGroups/type.ts"],"mappings":";iBAAiB;WACN;WACA;WACA;WACA;;iBAGM;WACN;WACA;WACA;WACA;WACA;;iBAGM;WACN;WACA,uBAAuB,mBAAmB,OAAO;WACjD,sBAAsB,iBAAiB;;iBAGjC;EACf,OAAO,iBAAiB,wBAAwB,6BAA6B;EAC7E,OAAO,UAAU,uBAAuB;EACxC,IAAI,kBAAkB;EACtB,UAAU,mCAAmC;EAC7C,WAAW,iBAAiB,yBAAyB;EACrD;;qBAGW,kCAAkC;EAC7C,YAAY,iBAAiB,kBAAkB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/services/sessionGroups/type.d.ts
|
|
2
|
+
export interface SessionCommunicationGroup {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly ownerAuthority: string;
|
|
5
|
+
readonly revision: number;
|
|
6
|
+
readonly members: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface SessionGroupMutation {
|
|
9
|
+
readonly groupId: string;
|
|
10
|
+
readonly authority: string;
|
|
11
|
+
readonly expectedRevision: number;
|
|
12
|
+
readonly add?: readonly string[];
|
|
13
|
+
readonly remove?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export interface SessionGroupStoreOptions {
|
|
16
|
+
readonly databasePath: string;
|
|
17
|
+
readonly authorizeManagement?: (authority: string, group: SessionCommunicationGroup) => boolean;
|
|
18
|
+
readonly validateEnrollment?: (groupId: string, memberReference: string) => boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface SessionGroupStore {
|
|
21
|
+
create(groupId: string, ownerAuthority: string, members: readonly string[]): SessionCommunicationGroup;
|
|
22
|
+
mutate(mutation: SessionGroupMutation): SessionCommunicationGroup;
|
|
23
|
+
get(groupId: string): SessionCommunicationGroup | undefined;
|
|
24
|
+
groupsFor(memberReference: string): readonly SessionCommunicationGroup[];
|
|
25
|
+
canMessage(groupId: string, sourceReference: string, targetReference: string): boolean;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare class SessionGroupRevisionError extends Error {
|
|
29
|
+
constructor(groupId: string, expected: number, actual: number);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=type.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.d.mts","names":[],"sources":["../../../../src/services/sessionGroups/type.ts"],"mappings":";iBAAiB;WACN;WACA;WACA;WACA;;iBAGM;WACN;WACA;WACA;WACA;WACA;;iBAGM;WACN;WACA,uBAAuB,mBAAmB,OAAO;WACjD,sBAAsB,iBAAiB;;iBAGjC;EACf,OAAO,iBAAiB,wBAAwB,6BAA6B;EAC7E,OAAO,UAAU,uBAAuB;EACxC,IAAI,kBAAkB;EACtB,UAAU,mCAAmC;EAC7C,WAAW,iBAAiB,yBAAyB;EACrD;;qBAGW,kCAAkC;EAC7C,YAAY,iBAAiB,kBAAkB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/services/sessionGroups/type.ts
|
|
2
|
+
var SessionGroupRevisionError = class extends Error {
|
|
3
|
+
constructor(groupId, expected, actual) {
|
|
4
|
+
super(`Session group '${groupId}' revision conflict: expected ${expected}, current ${actual}.`);
|
|
5
|
+
this.name = "SessionGroupRevisionError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { SessionGroupRevisionError };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=type.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.mjs","names":[],"sources":["../../../../src/services/sessionGroups/type.ts"],"sourcesContent":["export interface SessionCommunicationGroup {\n readonly id: string;\n readonly ownerAuthority: string;\n readonly revision: number;\n readonly members: readonly string[];\n}\n\nexport interface SessionGroupMutation {\n readonly groupId: string;\n readonly authority: string;\n readonly expectedRevision: number;\n readonly add?: readonly string[];\n readonly remove?: readonly string[];\n}\n\nexport interface SessionGroupStoreOptions {\n readonly databasePath: string;\n readonly authorizeManagement?: (authority: string, group: SessionCommunicationGroup) => boolean;\n readonly validateEnrollment?: (groupId: string, memberReference: string) => boolean;\n}\n\nexport interface SessionGroupStore {\n create(groupId: string, ownerAuthority: string, members: readonly string[]): SessionCommunicationGroup;\n mutate(mutation: SessionGroupMutation): SessionCommunicationGroup;\n get(groupId: string): SessionCommunicationGroup | undefined;\n groupsFor(memberReference: string): readonly SessionCommunicationGroup[];\n canMessage(groupId: string, sourceReference: string, targetReference: string): boolean;\n close(): void;\n}\n\nexport class SessionGroupRevisionError extends Error {\n constructor(groupId: string, expected: number, actual: number) {\n super(`Session group '${groupId}' revision conflict: expected ${expected}, current ${actual}.`);\n this.name = 'SessionGroupRevisionError';\n }\n}\n"],"mappings":";AA8BA,IAAa,4BAAb,cAA+C,MAAM;CACnD,YAAY,SAAiB,UAAkB,QAAgB;EAC7D,MAAM,kBAAkB,QAAQ,gCAAgC,SAAS,YAAY,OAAO,EAAE;EAC9F,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const require_type = require("./type.cjs");
|
|
2
|
+
//#region src/services/sessionIntercom/index.ts
|
|
3
|
+
const MESSAGE_KIND = "session-message";
|
|
4
|
+
const REPORT_KIND = "session-report";
|
|
5
|
+
function metadataFor(options, request, reportStatus) {
|
|
6
|
+
const group = options.groups.get(request.groupId);
|
|
7
|
+
if (!group || !options.groups.canMessage(request.groupId, options.selfReference, request.targetReference)) throw new Error(`Session group '${request.groupId}' does not authorize this message.`);
|
|
8
|
+
return {
|
|
9
|
+
sourceReference: options.selfReference,
|
|
10
|
+
targetReference: request.targetReference,
|
|
11
|
+
groupId: request.groupId,
|
|
12
|
+
groupRevision: String(group.revision),
|
|
13
|
+
...request.correlationId === void 0 ? {} : { correlationId: request.correlationId },
|
|
14
|
+
...reportStatus === void 0 ? {} : { reportStatus }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Agent-facing intercom operations. A composition may acquire this service without gaining session controls. */
|
|
18
|
+
function createSessionIntercom(options) {
|
|
19
|
+
const deliver = async (request, kind, status) => {
|
|
20
|
+
const recipientKey = options.directory.resolveDeliveryTarget(options.selfReference, request.targetReference);
|
|
21
|
+
if (!recipientKey) throw new Error(`Session '${request.targetReference}' is not discoverable.`);
|
|
22
|
+
const metadata = metadataFor(options, request, status);
|
|
23
|
+
return options.delivery.deliver({
|
|
24
|
+
...request.deliveryId === void 0 ? {} : { deliveryId: request.deliveryId },
|
|
25
|
+
recipientKey,
|
|
26
|
+
kind,
|
|
27
|
+
prompt: `[Session ${status === void 0 ? "message" : `report: ${status}`} from ${options.selfReference}] ${request.text}`,
|
|
28
|
+
metadata
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
discover() {
|
|
33
|
+
const members = new Set(options.groups.groupsFor(options.selfReference).flatMap((group) => group.members).filter((reference) => reference !== options.selfReference));
|
|
34
|
+
return options.directory.discover(options.selfReference, members);
|
|
35
|
+
},
|
|
36
|
+
send: (request) => deliver(request, MESSAGE_KIND),
|
|
37
|
+
report: (request) => deliver(request, REPORT_KIND, request.status),
|
|
38
|
+
messages() {
|
|
39
|
+
return options.delivery.inbox().filter((entry) => entry.kind === MESSAGE_KIND || entry.kind === REPORT_KIND).filter((entry) => {
|
|
40
|
+
const groupId = entry.metadata.groupId;
|
|
41
|
+
return groupId !== void 0 && options.groups.get(groupId)?.members.includes(options.selfReference) === true;
|
|
42
|
+
}).flatMap((entry) => {
|
|
43
|
+
const sourceReference = entry.metadata.sourceReference;
|
|
44
|
+
const targetReference = entry.metadata.targetReference;
|
|
45
|
+
const groupId = entry.metadata.groupId;
|
|
46
|
+
if (!sourceReference || !targetReference || !groupId) return [];
|
|
47
|
+
const reportStatus = entry.metadata.reportStatus;
|
|
48
|
+
return [{
|
|
49
|
+
deliveryId: entry.deliveryId,
|
|
50
|
+
sourceReference,
|
|
51
|
+
targetReference,
|
|
52
|
+
groupId,
|
|
53
|
+
...entry.metadata.correlationId === void 0 ? {} : { correlationId: entry.metadata.correlationId },
|
|
54
|
+
text: entry.prompt,
|
|
55
|
+
...reportStatus === "done" || reportStatus === "blocked" || reportStatus === "failed" ? { reportStatus } : {},
|
|
56
|
+
state: entry.state,
|
|
57
|
+
consumed: entry.consumed
|
|
58
|
+
}];
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
delivery(deliveryId) {
|
|
62
|
+
const entry = options.delivery.outbox(deliveryId);
|
|
63
|
+
const groupId = entry?.metadata.groupId;
|
|
64
|
+
return entry && groupId !== void 0 && options.groups.get(groupId)?.members.includes(options.selfReference) === true ? entry : void 0;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Builds the dynamic delivery check used before every send, retry, and recipient acceptance. */
|
|
69
|
+
function createSessionGroupDeliveryAuthorizer(options) {
|
|
70
|
+
return (peerKey, metadata) => {
|
|
71
|
+
const sourceReference = metadata?.sourceReference;
|
|
72
|
+
const targetReference = metadata?.targetReference;
|
|
73
|
+
const groupId = metadata?.groupId;
|
|
74
|
+
if (sourceReference && targetReference && groupId) {
|
|
75
|
+
const peerReference = options.peerReference(peerKey);
|
|
76
|
+
if (!peerReference) return false;
|
|
77
|
+
return (sourceReference === options.selfReference && targetReference === peerReference || targetReference === options.selfReference && sourceReference === peerReference) && options.groups.canMessage(groupId, sourceReference, targetReference);
|
|
78
|
+
}
|
|
79
|
+
return options.fallback?.(peerKey, metadata) === true;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
exports.DOOM_SESSION_INTERCOM_SERVICE = require_type.DOOM_SESSION_INTERCOM_SERVICE;
|
|
84
|
+
exports.createSessionGroupDeliveryAuthorizer = createSessionGroupDeliveryAuthorizer;
|
|
85
|
+
exports.createSessionIntercom = createSessionIntercom;
|
|
86
|
+
exports.readSessionIntercom = require_type.readSessionIntercom;
|
|
87
|
+
exports.requireSessionIntercom = require_type.requireSessionIntercom;
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/sessionIntercom/index.ts"],"sourcesContent":["import type { DoomSessionDeliveryMetadata } from '../sessionDelivery';\nimport type { SessionIntercomMessage } from './type';\nimport type {\n SessionGroupDeliveryAuthorizationOptions,\n SessionIntercom,\n SessionIntercomOptions,\n SessionIntercomSendRequest,\n SessionReportStatus,\n} from './type';\n\nconst MESSAGE_KIND = 'session-message';\nconst REPORT_KIND = 'session-report';\n\nfunction metadataFor(\n options: SessionIntercomOptions,\n request: SessionIntercomSendRequest,\n reportStatus?: SessionReportStatus,\n): DoomSessionDeliveryMetadata {\n const group = options.groups.get(request.groupId);\n if (!group || !options.groups.canMessage(request.groupId, options.selfReference, request.targetReference))\n throw new Error(`Session group '${request.groupId}' does not authorize this message.`);\n return {\n sourceReference: options.selfReference,\n targetReference: request.targetReference,\n groupId: request.groupId,\n groupRevision: String(group.revision),\n ...(request.correlationId === undefined ? {} : { correlationId: request.correlationId }),\n ...(reportStatus === undefined ? {} : { reportStatus }),\n };\n}\n\n/** Agent-facing intercom operations. A composition may acquire this service without gaining session controls. */\nexport function createSessionIntercom(options: SessionIntercomOptions): SessionIntercom {\n const deliver = async (\n request: SessionIntercomSendRequest,\n kind: typeof MESSAGE_KIND | typeof REPORT_KIND,\n status?: SessionReportStatus,\n ): Promise<{ deliveryId: string }> => {\n const recipientKey = options.directory.resolveDeliveryTarget(options.selfReference, request.targetReference);\n if (!recipientKey) throw new Error(`Session '${request.targetReference}' is not discoverable.`);\n const metadata = metadataFor(options, request, status);\n return options.delivery.deliver({\n ...(request.deliveryId === undefined ? {} : { deliveryId: request.deliveryId }),\n recipientKey,\n kind,\n prompt: `[Session ${status === undefined ? 'message' : `report: ${status}`} from ${options.selfReference}] ${request.text}`,\n metadata,\n });\n };\n\n const intercom: SessionIntercom = {\n discover() {\n const members = new Set(\n options.groups\n .groupsFor(options.selfReference)\n .flatMap((group) => group.members)\n .filter((reference) => reference !== options.selfReference),\n );\n return options.directory.discover(options.selfReference, members);\n },\n send: (request) => deliver(request, MESSAGE_KIND),\n report: (request) => deliver(request, REPORT_KIND, request.status),\n messages() {\n return options.delivery\n .inbox()\n .filter((entry) => entry.kind === MESSAGE_KIND || entry.kind === REPORT_KIND)\n .filter((entry) => {\n const groupId = entry.metadata.groupId;\n return groupId !== undefined && options.groups.get(groupId)?.members.includes(options.selfReference) === true;\n })\n .flatMap((entry): SessionIntercomMessage[] => {\n const sourceReference = entry.metadata.sourceReference;\n const targetReference = entry.metadata.targetReference;\n const groupId = entry.metadata.groupId;\n if (!sourceReference || !targetReference || !groupId) return [];\n const reportStatus = entry.metadata.reportStatus;\n return [\n {\n deliveryId: entry.deliveryId,\n sourceReference,\n targetReference,\n groupId,\n ...(entry.metadata.correlationId === undefined ? {} : { correlationId: entry.metadata.correlationId }),\n text: entry.prompt,\n ...(reportStatus === 'done' || reportStatus === 'blocked' || reportStatus === 'failed'\n ? { reportStatus }\n : {}),\n state: entry.state,\n consumed: entry.consumed,\n },\n ];\n });\n },\n delivery(deliveryId) {\n const entry = options.delivery.outbox(deliveryId);\n const groupId = entry?.metadata.groupId;\n return entry &&\n groupId !== undefined &&\n options.groups.get(groupId)?.members.includes(options.selfReference) === true\n ? entry\n : undefined;\n },\n };\n return Object.freeze(intercom);\n}\n\n/** Builds the dynamic delivery check used before every send, retry, and recipient acceptance. */\nexport function createSessionGroupDeliveryAuthorizer(\n options: SessionGroupDeliveryAuthorizationOptions,\n): (peerKey: string, metadata: DoomSessionDeliveryMetadata | undefined) => boolean {\n return (peerKey, metadata) => {\n const sourceReference = metadata?.sourceReference;\n const targetReference = metadata?.targetReference;\n const groupId = metadata?.groupId;\n if (sourceReference && targetReference && groupId) {\n const peerReference = options.peerReference(peerKey);\n if (!peerReference) return false;\n const directionMatches =\n (sourceReference === options.selfReference && targetReference === peerReference) ||\n (targetReference === options.selfReference && sourceReference === peerReference);\n return directionMatches && options.groups.canMessage(groupId, sourceReference, targetReference);\n }\n return options.fallback?.(peerKey, metadata) === true;\n };\n}\n\nexport { DOOM_SESSION_INTERCOM_SERVICE, readSessionIntercom, requireSessionIntercom } from './type';\nexport type {\n SessionGroupDeliveryAuthorizationOptions,\n SessionIntercom,\n SessionIntercomMessage,\n SessionIntercomOptions,\n SessionIntercomSendRequest,\n SessionReportStatus,\n} from './type';\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DoomSessionDeliveryMetadata } from "../sessionDelivery/type.cjs";
|
|
2
|
+
import "../sessionDelivery/index.cjs";
|
|
3
|
+
import { DOOM_SESSION_INTERCOM_SERVICE, SessionGroupDeliveryAuthorizationOptions, SessionIntercom, SessionIntercomMessage, SessionIntercomOptions, SessionIntercomSendRequest, SessionReportStatus, readSessionIntercom, requireSessionIntercom } from "./type.cjs";
|
|
4
|
+
//#region src/services/sessionIntercom/index.d.ts
|
|
5
|
+
/** Agent-facing intercom operations. A composition may acquire this service without gaining session controls. */
|
|
6
|
+
export declare function createSessionIntercom(options: SessionIntercomOptions): SessionIntercom;
|
|
7
|
+
/** Builds the dynamic delivery check used before every send, retry, and recipient acceptance. */
|
|
8
|
+
export declare function createSessionGroupDeliveryAuthorizer(options: SessionGroupDeliveryAuthorizationOptions): (peerKey: string, metadata: DoomSessionDeliveryMetadata | undefined) => boolean;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { DOOM_SESSION_INTERCOM_SERVICE, type SessionGroupDeliveryAuthorizationOptions, type SessionIntercom, type SessionIntercomMessage, type SessionIntercomOptions, type SessionIntercomSendRequest, type SessionReportStatus, readSessionIntercom, requireSessionIntercom };
|
|
11
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/sessionIntercom/index.ts"],"mappings":";;;;;wBAgCgB,sBAAsB,SAAS,yBAAyB;;wBA2ExD,qCACd,SAAS,4CACP,iBAAiB,UAAU"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DoomSessionDeliveryMetadata } from "../sessionDelivery/type.mjs";
|
|
2
|
+
import "../sessionDelivery/index.mjs";
|
|
3
|
+
import { DOOM_SESSION_INTERCOM_SERVICE, SessionGroupDeliveryAuthorizationOptions, SessionIntercom, SessionIntercomMessage, SessionIntercomOptions, SessionIntercomSendRequest, SessionReportStatus, readSessionIntercom, requireSessionIntercom } from "./type.mjs";
|
|
4
|
+
//#region src/services/sessionIntercom/index.d.ts
|
|
5
|
+
/** Agent-facing intercom operations. A composition may acquire this service without gaining session controls. */
|
|
6
|
+
export declare function createSessionIntercom(options: SessionIntercomOptions): SessionIntercom;
|
|
7
|
+
/** Builds the dynamic delivery check used before every send, retry, and recipient acceptance. */
|
|
8
|
+
export declare function createSessionGroupDeliveryAuthorizer(options: SessionGroupDeliveryAuthorizationOptions): (peerKey: string, metadata: DoomSessionDeliveryMetadata | undefined) => boolean;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { DOOM_SESSION_INTERCOM_SERVICE, type SessionGroupDeliveryAuthorizationOptions, type SessionIntercom, type SessionIntercomMessage, type SessionIntercomOptions, type SessionIntercomSendRequest, type SessionReportStatus, readSessionIntercom, requireSessionIntercom };
|
|
11
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/sessionIntercom/index.ts"],"mappings":";;;;;wBAgCgB,sBAAsB,SAAS,yBAAyB;;wBA2ExD,qCACd,SAAS,4CACP,iBAAiB,UAAU"}
|