@happyvertical/smrt-reactions 0.0.0-stage → 0.57.23
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/AGENTS.md +5 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +38 -2
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/chunks/src-Cfi_AjeE.js +263 -0
- package/dist/chunks/src-Cfi_AjeE.js.map +1 -0
- package/dist/collections/index.d.ts +14 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/manifest.json +1265 -0
- package/dist/models/Poll.d.ts +28 -0
- package/dist/models/Poll.d.ts.map +1 -0
- package/dist/models/PollVote.d.ts +20 -0
- package/dist/models/PollVote.d.ts.map +1 -0
- package/dist/models/Reaction.d.ts +15 -0
- package/dist/models/Reaction.d.ts.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +323 -0
- package/dist/server.js.map +1 -0
- package/dist/services.d.ts +59 -0
- package/dist/services.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +480 -0
- package/dist/svelte/PollCard.svelte +40 -0
- package/dist/svelte/PollCard.svelte.d.ts +13 -0
- package/dist/svelte/PollCard.svelte.d.ts.map +1 -0
- package/dist/svelte/ReactionBar.svelte +33 -0
- package/dist/svelte/ReactionBar.svelte.d.ts +13 -0
- package/dist/svelte/ReactionBar.svelte.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +3 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +2 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/write-authority.d.ts +4 -0
- package/dist/write-authority.d.ts.map +1 -0
- package/package.json +67 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SmrtPolymorphicAssociation, type SmrtPolymorphicAssociationOptions } from '@happyvertical/smrt-core';
|
|
2
|
+
import type { PollOption, PollVisibility } from '../types.js';
|
|
3
|
+
export interface PollOptions extends SmrtPolymorphicAssociationOptions {
|
|
4
|
+
tenantId?: string;
|
|
5
|
+
question?: string;
|
|
6
|
+
optionsJson?: string;
|
|
7
|
+
opensAt?: Date | null;
|
|
8
|
+
closesAt?: Date | null;
|
|
9
|
+
visibility?: PollVisibility;
|
|
10
|
+
anonymousTally?: boolean;
|
|
11
|
+
closed?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class Poll extends SmrtPolymorphicAssociation {
|
|
14
|
+
tenantId: string;
|
|
15
|
+
question: string;
|
|
16
|
+
optionsJson: string;
|
|
17
|
+
opensAt: Date | null;
|
|
18
|
+
closesAt: Date | null;
|
|
19
|
+
visibility: PollVisibility;
|
|
20
|
+
oneVotePerActor: boolean;
|
|
21
|
+
anonymousTally: boolean;
|
|
22
|
+
closed: boolean;
|
|
23
|
+
constructor(options?: PollOptions);
|
|
24
|
+
assertWriteAuthority(): void;
|
|
25
|
+
protected validateBeforeSave(): Promise<void>;
|
|
26
|
+
get pollOptions(): PollOption[];
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=Poll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Poll.d.ts","sourceRoot":"","sources":["../../src/models/Poll.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,0BAA0B,EAC1B,KAAK,iCAAiC,EAGvC,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,WAAY,SAAQ,iCAAiC;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AACD,qBAca,IAAK,SAAQ,0BAA0B;IACtC,QAAQ,SAAM;IACC,QAAQ,SAAM;IACA,WAAW,SAAQ;IACf,OAAO,EAAE,IAAI,GAAG,IAAI,CAAQ;IAC5B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAQ;IACjE,UAAU,EAAE,cAAc,CAAY;IACtC,eAAe,UAAQ;IACvB,cAAc,UAAQ;IACtB,MAAM,UAAS;gBACZ,OAAO,GAAE,WAAgB;IAarC,oBAAoB,IAAI,IAAI;cAGH,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5D,IAAI,WAAW,IAAI,UAAU,EAAE,CAkB9B;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SmrtClassOptions, SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
export interface PollVoteOptions extends SmrtClassOptions {
|
|
3
|
+
tenantId?: string;
|
|
4
|
+
pollId?: string;
|
|
5
|
+
optionId?: string;
|
|
6
|
+
actorProfileId?: string;
|
|
7
|
+
participationSnapshot?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Append-only through PollService; no generated write API. */
|
|
10
|
+
export declare class PollVote extends SmrtObject {
|
|
11
|
+
tenantId: string;
|
|
12
|
+
pollId: string;
|
|
13
|
+
actorProfileId: string;
|
|
14
|
+
optionId: string;
|
|
15
|
+
participationSnapshot: string;
|
|
16
|
+
constructor(options?: PollVoteOptions);
|
|
17
|
+
assertWriteAuthority(): void;
|
|
18
|
+
protected validateBeforeSave(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=PollVote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PollVote.d.ts","sourceRoot":"","sources":["../../src/models/PollVote.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,UAAU,EAGX,MAAM,0BAA0B,CAAC;AAIlC,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AACD,+DAA+D;AAC/D,qBAea,QAAS,SAAQ,UAAU;IAC1B,QAAQ,SAAM;IAM1B,MAAM,SAAM;IAEZ,cAAc,SAAM;IACuB,QAAQ,SAAM;IAEzD,qBAAqB,SAAQ;gBACjB,OAAO,GAAE,eAAoB;IAUzC,oBAAoB,IAAI,IAAI;cAGH,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B7D"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SmrtPolymorphicAssociation, type SmrtPolymorphicAssociationOptions } from '@happyvertical/smrt-core';
|
|
2
|
+
import type { ReactionKind } from '../types.js';
|
|
3
|
+
export declare class Reaction extends SmrtPolymorphicAssociation {
|
|
4
|
+
tenantId: string;
|
|
5
|
+
actorProfileId: string;
|
|
6
|
+
kind: ReactionKind;
|
|
7
|
+
constructor(options?: SmrtPolymorphicAssociationOptions & {
|
|
8
|
+
tenantId?: string;
|
|
9
|
+
actorProfileId?: string;
|
|
10
|
+
kind?: ReactionKind;
|
|
11
|
+
});
|
|
12
|
+
assertWriteAuthority(): void;
|
|
13
|
+
protected validateBeforeSave(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=Reaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reaction.d.ts","sourceRoot":"","sources":["../../src/models/Reaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,0BAA0B,EAC1B,KAAK,iCAAiC,EAEvC,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,qBAea,QAAS,SAAQ,0BAA0B;IAC1C,QAAQ,SAAM;IAE1B,cAAc,SAAM;IACO,IAAI,EAAE,YAAY,CAAU;gBAErD,OAAO,GAAE,iCAAiC,GAAG;QAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,IAAI,CAAC,EAAE,YAAY,CAAC;KAChB;IAQR,oBAAoB,IAAI,IAAI;cAGH,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7D"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,wBAAwB,CAAC;AAEhC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { a as PollVote, i as Reaction, n as PollVoteCollection, o as Poll, r as ReactionCollection, s as withParticipationWrite, t as PollCollection } from "./chunks/src-Cfi_AjeE.js";
|
|
2
|
+
import { REACTION_KINDS } from "./types.js";
|
|
3
|
+
import { ObjectRegistry } from "@happyvertical/smrt-core";
|
|
4
|
+
import { withTenant } from "@happyvertical/smrt-tenancy";
|
|
5
|
+
import { ParticipationDeniedError, ParticipationService } from "@happyvertical/smrt-comments/participation";
|
|
6
|
+
import { getDatabase } from "@happyvertical/sql";
|
|
7
|
+
//#region src/services.ts
|
|
8
|
+
var runtimeToken = /* @__PURE__ */ Symbol("participation-runtime");
|
|
9
|
+
var canonical = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) ? value.toLowerCase() : value;
|
|
10
|
+
var table = (name) => `"${name.replace(/"/g, "\"\"")}"`;
|
|
11
|
+
var ParticipationBoundary = class {
|
|
12
|
+
options;
|
|
13
|
+
#backend;
|
|
14
|
+
constructor(options, token) {
|
|
15
|
+
if (token !== runtimeToken) throw new TypeError("Use ParticipationRuntime.create.");
|
|
16
|
+
this.#backend = options.backend;
|
|
17
|
+
if (!options.actor || [
|
|
18
|
+
options.actor.tenantId,
|
|
19
|
+
options.actor.userId,
|
|
20
|
+
options.actor.profileId
|
|
21
|
+
].some((v) => typeof v !== "string" || !v.trim()) || !options.participation?.authorize || typeof options.authorizeRecord !== "function") throw new TypeError("Trusted actor, record authorizer and participation authorizer are required.");
|
|
22
|
+
this.options = {
|
|
23
|
+
...options,
|
|
24
|
+
actor: {
|
|
25
|
+
tenantId: canonical(options.actor.tenantId),
|
|
26
|
+
userId: canonical(options.actor.userId),
|
|
27
|
+
profileId: canonical(options.actor.profileId)
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async access(recordType, recordId, action) {
|
|
32
|
+
if (!recordType?.trim() || !recordId?.trim() || await this.options.authorizeRecord({
|
|
33
|
+
...this.options.actor,
|
|
34
|
+
recordType,
|
|
35
|
+
recordId,
|
|
36
|
+
action
|
|
37
|
+
}) !== true) throw new ParticipationDeniedError("Record access denied.");
|
|
38
|
+
}
|
|
39
|
+
request(recordType, recordId, action) {
|
|
40
|
+
return {
|
|
41
|
+
tenantId: this.options.actor.tenantId,
|
|
42
|
+
actorUserId: this.options.actor.userId,
|
|
43
|
+
recordType,
|
|
44
|
+
recordId,
|
|
45
|
+
action
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
guard(db = this.options.db) {
|
|
49
|
+
return new ParticipationService({
|
|
50
|
+
db,
|
|
51
|
+
entitlement: this.options.participation,
|
|
52
|
+
now: this.options.now
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
scoped(work) {
|
|
56
|
+
if (!["sqlite", "postgres"].includes(this.#backend)) throw new TypeError("Unsupported participation backend.");
|
|
57
|
+
return withTenant({ tenantId: this.options.actor.tenantId }, work);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var ReactionService = class extends ParticipationBoundary {
|
|
61
|
+
/** Explicit desired state is retry-safe; retraction applies only to the authenticated profile. */
|
|
62
|
+
async setReaction(recordType, recordId, kind, active) {
|
|
63
|
+
if (!REACTION_KINDS.includes(kind) || typeof active !== "boolean") throw new TypeError("Invalid reaction.");
|
|
64
|
+
recordId = canonical(recordId);
|
|
65
|
+
recordType = await ObjectRegistry.resolveQualifiedNameAsync(recordType) ?? recordType;
|
|
66
|
+
await this.access(recordType, recordId, "react");
|
|
67
|
+
await this.scoped(async () => {
|
|
68
|
+
const { tenantId, profileId } = this.options.actor;
|
|
69
|
+
const request = this.request(recordType, recordId, "react");
|
|
70
|
+
await this.guard().authorize(request);
|
|
71
|
+
const collection = await ReactionCollection.create({ db: this.options.db });
|
|
72
|
+
if (!collection.db.transaction) throw new Error("Reactions require transactions.");
|
|
73
|
+
await collection.db.transaction(async (tx) => {
|
|
74
|
+
const scoped = await ReactionCollection.create({ db: tx });
|
|
75
|
+
const existing = await scoped.list({ where: {
|
|
76
|
+
tenantId,
|
|
77
|
+
metaType: recordType,
|
|
78
|
+
metaId: recordId,
|
|
79
|
+
actorProfileId: profileId,
|
|
80
|
+
kind
|
|
81
|
+
} });
|
|
82
|
+
if (!active) {
|
|
83
|
+
await this.guard(tx).authorize(request);
|
|
84
|
+
await tx.query(`DELETE FROM ${table(scoped.tableName)} WHERE tenant_id = ? AND meta_type = ? AND meta_id = ? AND actor_profile_id = ? AND kind = ?`, tenantId, recordType, recordId, profileId, kind);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (existing.length) {
|
|
88
|
+
await this.guard(tx).authorize(request);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const id = crypto.randomUUID();
|
|
92
|
+
if ((await tx.query(`INSERT INTO ${table(scoped.tableName)} (id, slug, context, role, tenant_id, meta_type, meta_id, actor_profile_id, kind) VALUES (?, ?, '', 'default', ?, ?, ?, ?, ?) ON CONFLICT (tenant_id, meta_type, meta_id, actor_profile_id, kind) DO NOTHING RETURNING id`, id, id, tenantId, recordType, recordId, profileId, kind)).rows.length === 0) {
|
|
93
|
+
await this.guard(tx).authorize(request);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await this.guard(tx).executeInTransaction(tx, request, async () => void 0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
retract(recordType, recordId, kind) {
|
|
101
|
+
return this.setReaction(recordType, recordId, kind, false);
|
|
102
|
+
}
|
|
103
|
+
async tally(recordType, recordId) {
|
|
104
|
+
recordId = canonical(recordId);
|
|
105
|
+
recordType = await ObjectRegistry.resolveQualifiedNameAsync(recordType) ?? recordType;
|
|
106
|
+
await this.access(recordType, recordId, "read");
|
|
107
|
+
return this.scoped(async () => {
|
|
108
|
+
const collection = await ReactionCollection.create({ db: this.options.db });
|
|
109
|
+
const where = {
|
|
110
|
+
tenantId: this.options.actor.tenantId,
|
|
111
|
+
metaType: recordType,
|
|
112
|
+
metaId: recordId
|
|
113
|
+
};
|
|
114
|
+
const own = await collection.list({ where: {
|
|
115
|
+
...where,
|
|
116
|
+
actorProfileId: this.options.actor.profileId
|
|
117
|
+
} });
|
|
118
|
+
return Promise.all(REACTION_KINDS.map(async (kind) => ({
|
|
119
|
+
kind,
|
|
120
|
+
count: await collection.count({ where: {
|
|
121
|
+
...where,
|
|
122
|
+
kind
|
|
123
|
+
} }),
|
|
124
|
+
selected: own.some((r) => r.kind === kind)
|
|
125
|
+
})));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var PollService = class extends ParticipationBoundary {
|
|
130
|
+
time() {
|
|
131
|
+
const value = (this.options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
132
|
+
if (!Number.isFinite(value)) throw new TypeError("Invalid clock.");
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
isClosed(poll) {
|
|
136
|
+
const now = this.time();
|
|
137
|
+
const opens = poll.opensAt === null ? null : new Date(poll.opensAt).getTime();
|
|
138
|
+
const closes = poll.closesAt === null ? null : new Date(poll.closesAt).getTime();
|
|
139
|
+
if (opens !== null && !Number.isFinite(opens) || closes !== null && !Number.isFinite(closes)) throw new TypeError("Invalid poll window.");
|
|
140
|
+
return poll.closed || opens !== null && now < opens || closes !== null && now >= closes;
|
|
141
|
+
}
|
|
142
|
+
async load(db, id, action) {
|
|
143
|
+
const poll = await (await PollCollection.create({ db })).get(canonical(id));
|
|
144
|
+
if (!poll?.id || canonical(poll.tenantId) !== this.options.actor.tenantId) throw new ParticipationDeniedError("Poll unavailable.");
|
|
145
|
+
await this.access(poll.metaType, poll.metaId, action);
|
|
146
|
+
if (![
|
|
147
|
+
"always",
|
|
148
|
+
"after_vote",
|
|
149
|
+
"after_close"
|
|
150
|
+
].includes(poll.visibility) || poll.oneVotePerActor !== true || typeof poll.anonymousTally !== "boolean") throw new TypeError("Invalid poll policy.");
|
|
151
|
+
poll.pollOptions;
|
|
152
|
+
return poll;
|
|
153
|
+
}
|
|
154
|
+
async locked(pollId, action, work) {
|
|
155
|
+
return this.scoped(async () => {
|
|
156
|
+
const collection = await PollCollection.create({ db: this.options.db });
|
|
157
|
+
if (!collection.db.transaction) throw new Error("Polls require transactions.");
|
|
158
|
+
return collection.db.transaction(async (tx) => {
|
|
159
|
+
const scoped = await PollCollection.create({ db: tx });
|
|
160
|
+
await tx.query(`UPDATE ${table(scoped.tableName)} SET closed = closed WHERE id = ? AND tenant_id = ?`, canonical(pollId), this.options.actor.tenantId);
|
|
161
|
+
return work(tx, await this.load(tx, pollId, action));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async create(input) {
|
|
166
|
+
await this.access(input.recordType, input.recordId, "manage");
|
|
167
|
+
if (!input.question?.trim() || !Array.isArray(input.options) || input.options.length < 2 || input.options.length > 50 || input.options.some((o) => !o || typeof o.id !== "string" || !o.id.trim() || typeof o.label !== "string" || !o.label.trim()) || new Set(input.options.map((o) => o.id)).size !== input.options.length) throw new TypeError("Poll requires a question and 2–50 unique options.");
|
|
168
|
+
if (input.visibility !== void 0 && ![
|
|
169
|
+
"always",
|
|
170
|
+
"after_vote",
|
|
171
|
+
"after_close"
|
|
172
|
+
].includes(input.visibility)) throw new TypeError("Invalid poll visibility.");
|
|
173
|
+
if (input.anonymousTally !== void 0 && typeof input.anonymousTally !== "boolean") throw new TypeError("Invalid tally policy.");
|
|
174
|
+
for (const date of [input.opensAt, input.closesAt]) if (date != null && (!(date instanceof Date) || !Number.isFinite(date.getTime()))) throw new TypeError("Invalid poll window.");
|
|
175
|
+
if (input.opensAt && input.closesAt && input.opensAt >= input.closesAt) throw new TypeError("Poll closesAt must follow opensAt.");
|
|
176
|
+
return this.scoped(async () => {
|
|
177
|
+
const collection = await PollCollection.create({ db: this.options.db });
|
|
178
|
+
if (!collection.db.transaction) throw new Error("Polls require transactions.");
|
|
179
|
+
return collection.db.transaction((tx) => withParticipationWrite(tx, async () => (await PollCollection.create({ db: tx })).create({
|
|
180
|
+
tenantId: this.options.actor.tenantId,
|
|
181
|
+
metaType: input.recordType,
|
|
182
|
+
metaId: canonical(input.recordId),
|
|
183
|
+
question: input.question.trim(),
|
|
184
|
+
optionsJson: JSON.stringify(input.options.map((o) => ({
|
|
185
|
+
id: o.id,
|
|
186
|
+
label: o.label.trim()
|
|
187
|
+
}))),
|
|
188
|
+
opensAt: input.opensAt ?? null,
|
|
189
|
+
closesAt: input.closesAt ?? null,
|
|
190
|
+
visibility: input.visibility ?? "always",
|
|
191
|
+
anonymousTally: input.anonymousTally ?? true
|
|
192
|
+
})));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async setClosed(pollId, closed) {
|
|
196
|
+
if (typeof closed !== "boolean") throw new TypeError("Invalid closed state.");
|
|
197
|
+
await this.locked(pollId, "manage", async (tx, poll) => {
|
|
198
|
+
const collection = await PollCollection.create({ db: tx });
|
|
199
|
+
await tx.query(`UPDATE ${table(collection.tableName)} SET closed = ? WHERE id = ? AND tenant_id = ?`, closed, poll.id, this.options.actor.tenantId);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
close(pollId) {
|
|
203
|
+
return this.setClosed(pollId, true);
|
|
204
|
+
}
|
|
205
|
+
reopen(pollId) {
|
|
206
|
+
return this.setClosed(pollId, false);
|
|
207
|
+
}
|
|
208
|
+
/** Immutable first choice; identical retry reauthorizes but never replaces evidence. */
|
|
209
|
+
async vote(pollId, optionId) {
|
|
210
|
+
if (typeof optionId !== "string" || !optionId.trim()) throw new TypeError("Option required.");
|
|
211
|
+
await this.locked(pollId, "vote", async (tx, poll) => {
|
|
212
|
+
if (!poll.pollOptions.some((o) => o.id === optionId)) throw new TypeError("Unknown poll option.");
|
|
213
|
+
const request = this.request(poll.metaType, poll.metaId, "vote");
|
|
214
|
+
const guard = this.guard(tx);
|
|
215
|
+
await guard.authorize(request);
|
|
216
|
+
const votes = await PollVoteCollection.create({ db: tx });
|
|
217
|
+
const where = {
|
|
218
|
+
tenantId: this.options.actor.tenantId,
|
|
219
|
+
pollId: poll.id ?? pollId,
|
|
220
|
+
actorProfileId: this.options.actor.profileId
|
|
221
|
+
};
|
|
222
|
+
const [existing] = await votes.list({ where });
|
|
223
|
+
if (existing) {
|
|
224
|
+
if (existing.optionId !== optionId) throw new ParticipationDeniedError("Actor already voted.");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (this.isClosed(poll)) throw new ParticipationDeniedError("Poll is not open.");
|
|
228
|
+
await guard.executeInTransaction(tx, request, async (guarded, decision) => {
|
|
229
|
+
if (!decision.snapshot || typeof decision.snapshot !== "object" || Array.isArray(decision.snapshot)) throw new ParticipationDeniedError("Participation snapshot required.");
|
|
230
|
+
const snapshot = JSON.stringify(decision.snapshot);
|
|
231
|
+
const retained = snapshot ? JSON.parse(snapshot) : null;
|
|
232
|
+
if (!retained || typeof retained !== "object" || Array.isArray(retained) || Object.keys(retained).length === 0) throw new ParticipationDeniedError("Participation snapshot required.");
|
|
233
|
+
await withParticipationWrite(guarded, async () => (await PollVoteCollection.create({ db: guarded })).create({
|
|
234
|
+
...where,
|
|
235
|
+
optionId,
|
|
236
|
+
participationSnapshot: snapshot
|
|
237
|
+
}));
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** Authorized aggregate projection never includes voter IDs or snapshots. */
|
|
242
|
+
async results(pollId) {
|
|
243
|
+
return this.scoped(async () => {
|
|
244
|
+
const poll = await this.load(this.options.db, pollId, "read");
|
|
245
|
+
const votes = await PollVoteCollection.create({ db: this.options.db });
|
|
246
|
+
const where = {
|
|
247
|
+
tenantId: this.options.actor.tenantId,
|
|
248
|
+
pollId: poll.id
|
|
249
|
+
};
|
|
250
|
+
const [own] = await votes.list({
|
|
251
|
+
where: {
|
|
252
|
+
...where,
|
|
253
|
+
actorProfileId: this.options.actor.profileId
|
|
254
|
+
},
|
|
255
|
+
limit: 1
|
|
256
|
+
});
|
|
257
|
+
const selectedOptionId = own?.optionId ?? null;
|
|
258
|
+
const closed = this.isClosed(poll);
|
|
259
|
+
const visible = poll.visibility === "always" || poll.visibility === "after_vote" && selectedOptionId !== null || poll.visibility === "after_close" && (poll.closed || poll.closesAt !== null && this.time() >= new Date(poll.closesAt).getTime());
|
|
260
|
+
const results = visible ? Object.fromEntries(await Promise.all(poll.pollOptions.map(async (o) => [o.id, await votes.count({ where: {
|
|
261
|
+
...where,
|
|
262
|
+
optionId: o.id
|
|
263
|
+
} })]))) : null;
|
|
264
|
+
return {
|
|
265
|
+
id: poll.id ?? pollId,
|
|
266
|
+
question: poll.question,
|
|
267
|
+
options: poll.pollOptions,
|
|
268
|
+
closed,
|
|
269
|
+
selectedOptionId,
|
|
270
|
+
results,
|
|
271
|
+
totalVotes: visible ? await votes.count({ where }) : null
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var ParticipationRuntime = class ParticipationRuntime {
|
|
277
|
+
#database;
|
|
278
|
+
reactions;
|
|
279
|
+
polls;
|
|
280
|
+
constructor(options, token) {
|
|
281
|
+
if (token !== runtimeToken) throw new TypeError("Use ParticipationRuntime.create.");
|
|
282
|
+
this.#database = options.db;
|
|
283
|
+
this.reactions = new ReactionService(options, runtimeToken);
|
|
284
|
+
this.polls = new PollService(options, runtimeToken);
|
|
285
|
+
}
|
|
286
|
+
get database() {
|
|
287
|
+
return this.#database;
|
|
288
|
+
}
|
|
289
|
+
static async create(options) {
|
|
290
|
+
const config = options?.database;
|
|
291
|
+
const keys = /* @__PURE__ */ new Set([
|
|
292
|
+
"type",
|
|
293
|
+
"url",
|
|
294
|
+
"cache",
|
|
295
|
+
"dbid",
|
|
296
|
+
"authToken",
|
|
297
|
+
"encryptionKey",
|
|
298
|
+
"transactionQueueTimeout",
|
|
299
|
+
"max",
|
|
300
|
+
"connectionTimeoutMillis",
|
|
301
|
+
"idleTimeoutMillis"
|
|
302
|
+
]);
|
|
303
|
+
if (!config || !["sqlite", "postgres"].includes(config.type) || typeof config.url !== "string" || !config.url.trim() || config.cache !== void 0 && config.cache !== false || Object.keys(config).some((key) => !keys.has(key))) throw new TypeError("Participation supports owned SQLite/PostgreSQL configurations only; DuckDB is blocked by SDK #1452.");
|
|
304
|
+
if (!options.actor || [
|
|
305
|
+
options.actor.tenantId,
|
|
306
|
+
options.actor.userId,
|
|
307
|
+
options.actor.profileId
|
|
308
|
+
].some((v) => typeof v !== "string" || !v.trim()) || typeof options.authorizeRecord !== "function" || typeof options.participation?.authorize !== "function") throw new TypeError("Trusted actor, record authorizer and participation authorizer are required.");
|
|
309
|
+
const db = await getDatabase({
|
|
310
|
+
...config,
|
|
311
|
+
cache: false
|
|
312
|
+
});
|
|
313
|
+
return new ParticipationRuntime({
|
|
314
|
+
...options,
|
|
315
|
+
db,
|
|
316
|
+
backend: config.type
|
|
317
|
+
}, runtimeToken);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
//#endregion
|
|
321
|
+
export { ParticipationRuntime, Poll, PollCollection, PollVote, PollVoteCollection, REACTION_KINDS, Reaction, ReactionCollection };
|
|
322
|
+
|
|
323
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/services.ts"],"sourcesContent":["import {\n ParticipationDeniedError,\n ParticipationService,\n} from '@happyvertical/smrt-comments/participation';\nimport {\n ObjectRegistry,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { withTenant } from '@happyvertical/smrt-tenancy';\nimport { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport {\n PollCollection,\n PollVoteCollection,\n ReactionCollection,\n} from './collections/index.js';\nimport type { Poll } from './models/Poll.js';\nimport {\n type CreatePollInput,\n type PollProjection,\n REACTION_KINDS,\n type ReactionKind,\n type ServiceOptions,\n} from './types.js';\nimport { withParticipationWrite } from './write-authority.js';\n\ntype Database = PollCollection['db'];\ntype RuntimeOptions = Omit<ServiceOptions, 'database'> & {\n db: DatabaseInterface;\n backend: 'sqlite' | 'postgres';\n};\nconst runtimeToken = Symbol('participation-runtime');\nconst canonical = (value: string) =>\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)\n ? value.toLowerCase()\n : value;\nconst table = (name: string) => `\"${name.replace(/\"/g, '\"\"')}\"`;\nclass ParticipationBoundary {\n protected readonly options: RuntimeOptions;\n readonly #backend: 'sqlite' | 'postgres';\n constructor(options: RuntimeOptions, token: symbol) {\n if (token !== runtimeToken)\n throw new TypeError('Use ParticipationRuntime.create.');\n this.#backend = options.backend;\n if (\n !options.actor ||\n [\n options.actor.tenantId,\n options.actor.userId,\n options.actor.profileId,\n ].some((v) => typeof v !== 'string' || !v.trim()) ||\n !options.participation?.authorize ||\n typeof options.authorizeRecord !== 'function'\n )\n throw new TypeError(\n 'Trusted actor, record authorizer and participation authorizer are required.',\n );\n this.options = {\n ...options,\n actor: {\n tenantId: canonical(options.actor.tenantId),\n userId: canonical(options.actor.userId),\n profileId: canonical(options.actor.profileId),\n },\n };\n }\n protected async access(\n recordType: string,\n recordId: string,\n action: 'read' | 'react' | 'vote' | 'manage',\n ) {\n if (\n !recordType?.trim() ||\n !recordId?.trim() ||\n (await this.options.authorizeRecord({\n ...this.options.actor,\n recordType,\n recordId,\n action,\n })) !== true\n )\n throw new ParticipationDeniedError('Record access denied.');\n }\n protected request(\n recordType: string,\n recordId: string,\n action: 'react' | 'vote',\n ) {\n return {\n tenantId: this.options.actor.tenantId,\n actorUserId: this.options.actor.userId,\n recordType,\n recordId,\n action,\n };\n }\n protected guard(db: SmrtClassOptions['db'] = this.options.db) {\n return new ParticipationService({\n db,\n entitlement: this.options.participation,\n now: this.options.now,\n });\n }\n protected scoped<T>(work: () => Promise<T>) {\n if (!['sqlite', 'postgres'].includes(this.#backend))\n throw new TypeError('Unsupported participation backend.');\n return withTenant({ tenantId: this.options.actor.tenantId }, work);\n }\n}\n\nclass ReactionService extends ParticipationBoundary {\n /** Explicit desired state is retry-safe; retraction applies only to the authenticated profile. */\n async setReaction(\n recordType: string,\n recordId: string,\n kind: ReactionKind,\n active: boolean,\n ): Promise<void> {\n if (!REACTION_KINDS.includes(kind) || typeof active !== 'boolean')\n throw new TypeError('Invalid reaction.');\n recordId = canonical(recordId);\n recordType =\n (await ObjectRegistry.resolveQualifiedNameAsync(recordType)) ??\n recordType;\n await this.access(recordType, recordId, 'react');\n await this.scoped(async () => {\n const { tenantId, profileId } = this.options.actor;\n const request = this.request(recordType, recordId, 'react');\n // Current bans and entitlement apply even to replay/retraction.\n await this.guard().authorize(request);\n const collection = await ReactionCollection.create({\n db: this.options.db,\n });\n if (!collection.db.transaction)\n throw new Error('Reactions require transactions.');\n await collection.db.transaction(async (tx) => {\n const scoped = await ReactionCollection.create({ db: tx });\n const existing = await scoped.list({\n where: {\n tenantId,\n metaType: recordType,\n metaId: recordId,\n actorProfileId: profileId,\n kind,\n },\n });\n if (!active) {\n await this.guard(tx).authorize(request);\n await tx.query(\n `DELETE FROM ${table(scoped.tableName)} WHERE tenant_id = ? AND meta_type = ? AND meta_id = ? AND actor_profile_id = ? AND kind = ?`,\n tenantId,\n recordType,\n recordId,\n profileId,\n kind,\n );\n return;\n }\n if (existing.length) {\n await this.guard(tx).authorize(request);\n return;\n }\n // Arbitrate identity before reserving quota: a concurrent replay must\n // not commit a reservation for an insert that lost uniqueness.\n const id = crypto.randomUUID();\n const inserted = await tx.query(\n `INSERT INTO ${table(scoped.tableName)} (id, slug, context, role, tenant_id, meta_type, meta_id, actor_profile_id, kind) VALUES (?, ?, '', 'default', ?, ?, ?, ?, ?) ON CONFLICT (tenant_id, meta_type, meta_id, actor_profile_id, kind) DO NOTHING RETURNING id`,\n id,\n id,\n tenantId,\n recordType,\n recordId,\n profileId,\n kind,\n );\n if (inserted.rows.length === 0) {\n await this.guard(tx).authorize(request);\n return;\n }\n // Authorization/reservation failure rolls this candidate insert back\n // with the owned transaction, including any transactional change feed.\n await this.guard(tx).executeInTransaction(\n tx,\n request,\n async () => undefined,\n );\n });\n });\n }\n retract(recordType: string, recordId: string, kind: ReactionKind) {\n return this.setReaction(recordType, recordId, kind, false);\n }\n async tally(recordType: string, recordId: string) {\n recordId = canonical(recordId);\n recordType =\n (await ObjectRegistry.resolveQualifiedNameAsync(recordType)) ??\n recordType;\n await this.access(recordType, recordId, 'read');\n return this.scoped(async () => {\n const collection = await ReactionCollection.create({\n db: this.options.db,\n });\n const where = {\n tenantId: this.options.actor.tenantId,\n metaType: recordType,\n metaId: recordId,\n };\n const own = await collection.list({\n where: { ...where, actorProfileId: this.options.actor.profileId },\n });\n return Promise.all(\n REACTION_KINDS.map(async (kind) => ({\n kind,\n count: await collection.count({ where: { ...where, kind } }),\n selected: own.some((r) => r.kind === kind),\n })),\n );\n });\n }\n}\n\nclass PollService extends ParticipationBoundary {\n private time() {\n const value = (this.options.now?.() ?? new Date()).getTime();\n if (!Number.isFinite(value)) throw new TypeError('Invalid clock.');\n return value;\n }\n private isClosed(poll: Poll) {\n const now = this.time();\n const opens =\n poll.opensAt === null ? null : new Date(poll.opensAt).getTime();\n const closes =\n poll.closesAt === null ? null : new Date(poll.closesAt).getTime();\n if (\n (opens !== null && !Number.isFinite(opens)) ||\n (closes !== null && !Number.isFinite(closes))\n )\n throw new TypeError('Invalid poll window.');\n return (\n poll.closed ||\n (opens !== null && now < opens) ||\n (closes !== null && now >= closes)\n );\n }\n private async load(\n db: SmrtClassOptions['db'],\n id: string,\n action: 'read' | 'vote' | 'manage',\n ) {\n const poll = await (await PollCollection.create({ db })).get(canonical(id));\n if (!poll?.id || canonical(poll.tenantId) !== this.options.actor.tenantId)\n throw new ParticipationDeniedError('Poll unavailable.');\n await this.access(poll.metaType, poll.metaId, action);\n if (\n !['always', 'after_vote', 'after_close'].includes(poll.visibility) ||\n poll.oneVotePerActor !== true ||\n typeof poll.anonymousTally !== 'boolean'\n )\n throw new TypeError('Invalid poll policy.');\n poll.pollOptions; // Validate stored options before any projection or mutation.\n return poll;\n }\n private async locked<T>(\n pollId: string,\n action: 'vote' | 'manage',\n work: (tx: Database, poll: Poll) => Promise<T>,\n ) {\n return this.scoped(async () => {\n const collection = await PollCollection.create({ db: this.options.db });\n if (!collection.db.transaction)\n throw new Error('Polls require transactions.');\n return collection.db.transaction(async (tx) => {\n const scoped = await PollCollection.create({ db: tx });\n await tx.query(\n `UPDATE ${table(scoped.tableName)} SET closed = closed WHERE id = ? AND tenant_id = ?`,\n canonical(pollId),\n this.options.actor.tenantId,\n );\n return work(tx, await this.load(tx, pollId, action));\n });\n });\n }\n async create(input: CreatePollInput): Promise<Poll> {\n await this.access(input.recordType, input.recordId, 'manage');\n if (\n !input.question?.trim() ||\n !Array.isArray(input.options) ||\n input.options.length < 2 ||\n input.options.length > 50 ||\n input.options.some(\n (o) =>\n !o ||\n typeof o.id !== 'string' ||\n !o.id.trim() ||\n typeof o.label !== 'string' ||\n !o.label.trim(),\n ) ||\n new Set(input.options.map((o) => o.id)).size !== input.options.length\n )\n throw new TypeError('Poll requires a question and 2–50 unique options.');\n if (\n input.visibility !== undefined &&\n !['always', 'after_vote', 'after_close'].includes(input.visibility)\n )\n throw new TypeError('Invalid poll visibility.');\n if (\n input.anonymousTally !== undefined &&\n typeof input.anonymousTally !== 'boolean'\n )\n throw new TypeError('Invalid tally policy.');\n for (const date of [input.opensAt, input.closesAt])\n if (\n date != null &&\n (!(date instanceof Date) || !Number.isFinite(date.getTime()))\n )\n throw new TypeError('Invalid poll window.');\n if (input.opensAt && input.closesAt && input.opensAt >= input.closesAt)\n throw new TypeError('Poll closesAt must follow opensAt.');\n return this.scoped(async () => {\n const collection = await PollCollection.create({ db: this.options.db });\n if (!collection.db.transaction)\n throw new Error('Polls require transactions.');\n return collection.db.transaction((tx) =>\n withParticipationWrite(tx, async () =>\n (await PollCollection.create({ db: tx })).create({\n tenantId: this.options.actor.tenantId,\n metaType: input.recordType,\n metaId: canonical(input.recordId),\n question: input.question.trim(),\n optionsJson: JSON.stringify(\n input.options.map((o) => ({ id: o.id, label: o.label.trim() })),\n ),\n opensAt: input.opensAt ?? null,\n closesAt: input.closesAt ?? null,\n visibility: input.visibility ?? 'always',\n anonymousTally: input.anonymousTally ?? true,\n }),\n ),\n );\n });\n }\n async setClosed(pollId: string, closed: boolean): Promise<void> {\n if (typeof closed !== 'boolean')\n throw new TypeError('Invalid closed state.');\n await this.locked(pollId, 'manage', async (tx, poll) => {\n const collection = await PollCollection.create({ db: tx });\n await tx.query(\n `UPDATE ${table(collection.tableName)} SET closed = ? WHERE id = ? AND tenant_id = ?`,\n closed,\n poll.id,\n this.options.actor.tenantId,\n );\n });\n }\n close(pollId: string) {\n return this.setClosed(pollId, true);\n }\n reopen(pollId: string) {\n return this.setClosed(pollId, false);\n }\n /** Immutable first choice; identical retry reauthorizes but never replaces evidence. */\n async vote(pollId: string, optionId: string): Promise<void> {\n if (typeof optionId !== 'string' || !optionId.trim())\n throw new TypeError('Option required.');\n await this.locked(pollId, 'vote', async (tx, poll) => {\n if (!poll.pollOptions.some((o) => o.id === optionId))\n throw new TypeError('Unknown poll option.');\n const request = this.request(poll.metaType, poll.metaId, 'vote');\n const guard = this.guard(tx);\n await guard.authorize(request);\n const votes = await PollVoteCollection.create({ db: tx });\n const where = {\n tenantId: this.options.actor.tenantId,\n pollId: poll.id ?? pollId,\n actorProfileId: this.options.actor.profileId,\n };\n const [existing] = await votes.list({ where });\n if (existing) {\n if (existing.optionId !== optionId)\n throw new ParticipationDeniedError('Actor already voted.');\n return;\n }\n if (this.isClosed(poll))\n throw new ParticipationDeniedError('Poll is not open.');\n await guard.executeInTransaction(\n tx,\n request,\n async (guarded, decision) => {\n if (\n !decision.snapshot ||\n typeof decision.snapshot !== 'object' ||\n Array.isArray(decision.snapshot)\n )\n throw new ParticipationDeniedError(\n 'Participation snapshot required.',\n );\n // Serialize now: subsequent changes to the host object cannot alter retained evidence.\n const snapshot = JSON.stringify(decision.snapshot);\n const retained: unknown = snapshot ? JSON.parse(snapshot) : null;\n if (\n !retained ||\n typeof retained !== 'object' ||\n Array.isArray(retained) ||\n Object.keys(retained).length === 0\n )\n throw new ParticipationDeniedError(\n 'Participation snapshot required.',\n );\n await withParticipationWrite(guarded, async () =>\n (await PollVoteCollection.create({ db: guarded })).create({\n ...where,\n optionId,\n participationSnapshot: snapshot,\n }),\n );\n },\n );\n });\n }\n /** Authorized aggregate projection never includes voter IDs or snapshots. */\n async results(pollId: string): Promise<PollProjection> {\n return this.scoped(async () => {\n const poll = await this.load(this.options.db, pollId, 'read');\n const votes = await PollVoteCollection.create({ db: this.options.db });\n const where = { tenantId: this.options.actor.tenantId, pollId: poll.id };\n const [own] = await votes.list({\n where: { ...where, actorProfileId: this.options.actor.profileId },\n limit: 1,\n });\n const selectedOptionId = own?.optionId ?? null;\n const closed = this.isClosed(poll);\n const visible =\n poll.visibility === 'always' ||\n (poll.visibility === 'after_vote' && selectedOptionId !== null) ||\n (poll.visibility === 'after_close' &&\n (poll.closed ||\n (poll.closesAt !== null &&\n this.time() >= new Date(poll.closesAt).getTime())));\n const results = visible\n ? Object.fromEntries(\n await Promise.all(\n poll.pollOptions.map(async (o) => [\n o.id,\n await votes.count({ where: { ...where, optionId: o.id } }),\n ]),\n ),\n )\n : null;\n return {\n id: poll.id ?? pollId,\n question: poll.question,\n options: poll.pollOptions,\n closed,\n selectedOptionId,\n results,\n totalVotes: visible ? await votes.count({ where }) : null,\n };\n });\n }\n}\n\n/** One owned connection shared by both services and exposed for host migrations. */\nexport class ParticipationRuntime {\n readonly #database: DatabaseInterface;\n readonly reactions: ReactionService;\n readonly polls: PollService;\n private constructor(options: RuntimeOptions, token: symbol) {\n if (token !== runtimeToken)\n throw new TypeError('Use ParticipationRuntime.create.');\n this.#database = options.db;\n this.reactions = new ReactionService(options, runtimeToken);\n this.polls = new PollService(options, runtimeToken);\n }\n get database(): DatabaseInterface {\n return this.#database;\n }\n static async create(options: ServiceOptions): Promise<ParticipationRuntime> {\n const config = options?.database;\n const keys = new Set([\n 'type',\n 'url',\n 'cache',\n 'dbid',\n 'authToken',\n 'encryptionKey',\n 'transactionQueueTimeout',\n 'max',\n 'connectionTimeoutMillis',\n 'idleTimeoutMillis',\n ]);\n if (\n !config ||\n !['sqlite', 'postgres'].includes(config.type) ||\n typeof config.url !== 'string' ||\n !config.url.trim() ||\n (config.cache !== undefined && config.cache !== false) ||\n Object.keys(config).some((key) => !keys.has(key))\n )\n throw new TypeError(\n 'Participation supports owned SQLite/PostgreSQL configurations only; DuckDB is blocked by SDK #1452.',\n );\n if (\n !options.actor ||\n [\n options.actor.tenantId,\n options.actor.userId,\n options.actor.profileId,\n ].some((v) => typeof v !== 'string' || !v.trim()) ||\n typeof options.authorizeRecord !== 'function' ||\n typeof options.participation?.authorize !== 'function'\n )\n throw new TypeError(\n 'Trusted actor, record authorizer and participation authorizer are required.',\n );\n const db = await getDatabase({ ...config, cache: false });\n return new ParticipationRuntime(\n { ...options, db, backend: config.type },\n runtimeToken,\n );\n }\n}\nexport type { PollService, ReactionService };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ParticipationService } from '@happyvertical/smrt-comments/participation';
|
|
2
|
+
import { type SmrtClassOptions } from '@happyvertical/smrt-core';
|
|
3
|
+
import { type DatabaseInterface } from '@happyvertical/sql';
|
|
4
|
+
import type { Poll } from './models/Poll.js';
|
|
5
|
+
import { type CreatePollInput, type PollProjection, type ReactionKind, type ServiceOptions } from './types.js';
|
|
6
|
+
type RuntimeOptions = Omit<ServiceOptions, 'database'> & {
|
|
7
|
+
db: DatabaseInterface;
|
|
8
|
+
backend: 'sqlite' | 'postgres';
|
|
9
|
+
};
|
|
10
|
+
declare class ParticipationBoundary {
|
|
11
|
+
#private;
|
|
12
|
+
protected readonly options: RuntimeOptions;
|
|
13
|
+
constructor(options: RuntimeOptions, token: symbol);
|
|
14
|
+
protected access(recordType: string, recordId: string, action: 'read' | 'react' | 'vote' | 'manage'): Promise<void>;
|
|
15
|
+
protected request(recordType: string, recordId: string, action: 'react' | 'vote'): {
|
|
16
|
+
tenantId: string;
|
|
17
|
+
actorUserId: string;
|
|
18
|
+
recordType: string;
|
|
19
|
+
recordId: string;
|
|
20
|
+
action: "react" | "vote";
|
|
21
|
+
};
|
|
22
|
+
protected guard(db?: SmrtClassOptions['db']): ParticipationService;
|
|
23
|
+
protected scoped<T>(work: () => Promise<T>): Promise<T>;
|
|
24
|
+
}
|
|
25
|
+
declare class ReactionService extends ParticipationBoundary {
|
|
26
|
+
/** Explicit desired state is retry-safe; retraction applies only to the authenticated profile. */
|
|
27
|
+
setReaction(recordType: string, recordId: string, kind: ReactionKind, active: boolean): Promise<void>;
|
|
28
|
+
retract(recordType: string, recordId: string, kind: ReactionKind): Promise<void>;
|
|
29
|
+
tally(recordType: string, recordId: string): Promise<{
|
|
30
|
+
kind: ReactionKind;
|
|
31
|
+
count: number;
|
|
32
|
+
selected: boolean;
|
|
33
|
+
}[]>;
|
|
34
|
+
}
|
|
35
|
+
declare class PollService extends ParticipationBoundary {
|
|
36
|
+
private time;
|
|
37
|
+
private isClosed;
|
|
38
|
+
private load;
|
|
39
|
+
private locked;
|
|
40
|
+
create(input: CreatePollInput): Promise<Poll>;
|
|
41
|
+
setClosed(pollId: string, closed: boolean): Promise<void>;
|
|
42
|
+
close(pollId: string): Promise<void>;
|
|
43
|
+
reopen(pollId: string): Promise<void>;
|
|
44
|
+
/** Immutable first choice; identical retry reauthorizes but never replaces evidence. */
|
|
45
|
+
vote(pollId: string, optionId: string): Promise<void>;
|
|
46
|
+
/** Authorized aggregate projection never includes voter IDs or snapshots. */
|
|
47
|
+
results(pollId: string): Promise<PollProjection>;
|
|
48
|
+
}
|
|
49
|
+
/** One owned connection shared by both services and exposed for host migrations. */
|
|
50
|
+
export declare class ParticipationRuntime {
|
|
51
|
+
#private;
|
|
52
|
+
readonly reactions: ReactionService;
|
|
53
|
+
readonly polls: PollService;
|
|
54
|
+
private constructor();
|
|
55
|
+
get database(): DatabaseInterface;
|
|
56
|
+
static create(options: ServiceOptions): Promise<ParticipationRuntime>;
|
|
57
|
+
}
|
|
58
|
+
export type { PollService, ReactionService };
|
|
59
|
+
//# sourceMappingURL=services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,oBAAoB,EACrB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,oBAAoB,CAAC;AAMzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAIpB,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,GAAG;IACvD,EAAE,EAAE,iBAAiB,CAAC;IACtB,OAAO,EAAE,QAAQ,GAAG,UAAU,CAAC;CAChC,CAAC;AAOF,cAAM,qBAAqB;;IACzB,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;gBAE/B,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM;cA0BlC,MAAM,CACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ;IAc9C,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GAAG,MAAM;;;;;;;IAU1B,SAAS,CAAC,KAAK,CAAC,EAAE,GAAE,gBAAgB,CAAC,IAAI,CAAmB;IAO5D,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;CAK3C;AAED,cAAM,eAAgB,SAAQ,qBAAqB;IACjD,kGAAkG;IAC5F,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAwEhB,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY;IAG1D,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;CA2BjD;AAED,cAAM,WAAY,SAAQ,qBAAqB;IAC7C,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,QAAQ;YAiBF,IAAI;YAkBJ,MAAM;IAoBd,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA2D7C,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAa/D,KAAK,CAAC,MAAM,EAAE,MAAM;IAGpB,MAAM,CAAC,MAAM,EAAE,MAAM;IAGrB,wFAAwF;IAClF,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0D3D,6EAA6E;IACvE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAuCvD;AAED,oFAAoF;AACpF,qBAAa,oBAAoB;;IAE/B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,OAAO;IAOP,IAAI,QAAQ,IAAI,iBAAiB,CAEhC;WACY,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA4C5E;AACD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC"}
|