@kernhq/module-quire 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract/events.d.ts +11 -0
- package/dist/contract/events.d.ts.map +1 -1
- package/dist/contract/events.js +2 -0
- package/dist/contract/events.js.map +1 -1
- package/dist/contract/models.d.ts +29 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +19 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/permissions.d.ts +7 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +8 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/router.d.ts +285 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +37 -1
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +319 -0
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +90 -16
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +61 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema.d.ts +206 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +38 -2
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/index.d.ts +3 -0
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +3 -0
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/versions.d.ts +124 -0
- package/dist/server/services/versions.d.ts.map +1 -0
- package/dist/server/services/versions.js +182 -0
- package/dist/server/services/versions.js.map +1 -0
- package/migrations/0002_page_versions.sql +25 -0
- package/migrations/meta/0002_snapshot.json +491 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/contract/events.ts +5 -0
- package/src/contract/models.ts +23 -0
- package/src/contract/permissions.ts +8 -0
- package/src/contract/router.ts +39 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { KernError, uuidv7 } from '@kernhq/kernel';
|
|
2
|
+
import { and, desc, eq, lt } from 'drizzle-orm';
|
|
3
|
+
import { pages, pageVersions } from '../schema.js';
|
|
4
|
+
import { documentNameOf } from './pages.js';
|
|
5
|
+
/** How much of the prose a version list shows without loading the document. */
|
|
6
|
+
const PREVIEW = 160;
|
|
7
|
+
export function toVersion(row, publishedId) {
|
|
8
|
+
return {
|
|
9
|
+
id: row.id,
|
|
10
|
+
workspaceId: row.workspaceId,
|
|
11
|
+
pageId: row.pageId,
|
|
12
|
+
kind: row.kind,
|
|
13
|
+
label: row.label,
|
|
14
|
+
preview: row.text.slice(0, PREVIEW),
|
|
15
|
+
size: row.size,
|
|
16
|
+
authorId: row.authorId,
|
|
17
|
+
createdAt: row.createdAt.toISOString(),
|
|
18
|
+
published: publishedId === row.id,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function quireVersions(kernel, access) {
|
|
22
|
+
/** The document's current state and a snapshot of it, from the collab service. */
|
|
23
|
+
async function snapshotOf(workspaceId, pageId) {
|
|
24
|
+
return kernel.call('collab.document.snapshot', {
|
|
25
|
+
name: documentNameOf({ workspaceId, id: pageId }),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
/**
|
|
30
|
+
* Write down what the page says now.
|
|
31
|
+
*
|
|
32
|
+
* Returns null when the document has never been written to — a page created and never opened
|
|
33
|
+
* has nothing to version, and an empty row in the history would be a lie about somebody having
|
|
34
|
+
* saved something.
|
|
35
|
+
*/
|
|
36
|
+
async capture(tx, workspaceId, pageId, opts) {
|
|
37
|
+
const taken = await snapshotOf(workspaceId, pageId).catch((err) => {
|
|
38
|
+
kernel.log.warn({ err: String(err), pageId }, 'could not snapshot the document');
|
|
39
|
+
return null;
|
|
40
|
+
});
|
|
41
|
+
if (!taken)
|
|
42
|
+
return null;
|
|
43
|
+
const [row] = await tx
|
|
44
|
+
.select({ text: pages.text })
|
|
45
|
+
.from(pages)
|
|
46
|
+
.where(and(eq(pages.workspaceId, workspaceId), eq(pages.id, pageId)))
|
|
47
|
+
.limit(1);
|
|
48
|
+
const state = Buffer.from(taken.state, 'base64');
|
|
49
|
+
const [version] = await tx
|
|
50
|
+
.insert(pageVersions)
|
|
51
|
+
.values({
|
|
52
|
+
id: uuidv7(),
|
|
53
|
+
workspaceId,
|
|
54
|
+
pageId,
|
|
55
|
+
kind: opts.kind,
|
|
56
|
+
label: opts.label ?? null,
|
|
57
|
+
state,
|
|
58
|
+
snapshot: Buffer.from(taken.snapshot, 'base64'),
|
|
59
|
+
text: row?.text ?? '',
|
|
60
|
+
size: state.length,
|
|
61
|
+
authorId: opts.authorId ?? null,
|
|
62
|
+
})
|
|
63
|
+
.returning();
|
|
64
|
+
return version ?? null;
|
|
65
|
+
},
|
|
66
|
+
async list(tx, workspaceId, pageId, limit, cursor) {
|
|
67
|
+
const page = await access.pageRow(tx, workspaceId, pageId);
|
|
68
|
+
const rows = await tx
|
|
69
|
+
.select()
|
|
70
|
+
.from(pageVersions)
|
|
71
|
+
.where(and(eq(pageVersions.workspaceId, workspaceId), eq(pageVersions.pageId, pageId), cursor ? lt(pageVersions.id, cursor) : undefined))
|
|
72
|
+
.orderBy(desc(pageVersions.id))
|
|
73
|
+
.limit(limit + 1);
|
|
74
|
+
const items = rows.slice(0, limit).map((r) => toVersion(r, page.publishedVersionId));
|
|
75
|
+
return { items, nextCursor: rows.length > limit ? (items.at(-1)?.id ?? null) : null };
|
|
76
|
+
},
|
|
77
|
+
async row(tx, workspaceId, versionId) {
|
|
78
|
+
const [row] = await tx
|
|
79
|
+
.select()
|
|
80
|
+
.from(pageVersions)
|
|
81
|
+
.where(and(eq(pageVersions.workspaceId, workspaceId), eq(pageVersions.id, versionId)))
|
|
82
|
+
.limit(1);
|
|
83
|
+
if (!row)
|
|
84
|
+
throw KernError.notFound('Version');
|
|
85
|
+
return row;
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Put an older version back.
|
|
89
|
+
*
|
|
90
|
+
* Two things make this safe to offer. The state it is about to replace is captured first, so
|
|
91
|
+
* restoring is never itself the thing that loses work; and the replacement goes through the
|
|
92
|
+
* collab service rather than the database, because `applyUpdate` would merge the two and bring
|
|
93
|
+
* every deleted paragraph back alongside the ones that replaced it.
|
|
94
|
+
*/
|
|
95
|
+
async restore(tx, principal, workspaceId, versionId) {
|
|
96
|
+
const version = await this.row(tx, workspaceId, versionId);
|
|
97
|
+
await access.pageRow(tx, workspaceId, version.pageId);
|
|
98
|
+
await this.capture(tx, workspaceId, version.pageId, {
|
|
99
|
+
kind: 'auto',
|
|
100
|
+
label: null,
|
|
101
|
+
authorId: principal.userId,
|
|
102
|
+
});
|
|
103
|
+
await kernel.call('collab.document.replace', {
|
|
104
|
+
name: documentNameOf({ workspaceId, id: version.pageId }),
|
|
105
|
+
state: Buffer.from(version.state).toString('base64'),
|
|
106
|
+
});
|
|
107
|
+
const restored = await this.capture(tx, workspaceId, version.pageId, {
|
|
108
|
+
kind: 'restore',
|
|
109
|
+
label: version.label,
|
|
110
|
+
authorId: principal.userId,
|
|
111
|
+
});
|
|
112
|
+
if (!restored)
|
|
113
|
+
throw KernError.badRequest('The document could not be restored');
|
|
114
|
+
return restored;
|
|
115
|
+
},
|
|
116
|
+
/** What a reader is served, once somebody decides it is ready. */
|
|
117
|
+
async publish(tx, principal, workspaceId, pageId, label) {
|
|
118
|
+
const page = await access.pageRow(tx, workspaceId, pageId);
|
|
119
|
+
if (page.kind !== 'page')
|
|
120
|
+
throw KernError.badRequest('Only a page has a published version; a live doc is always live');
|
|
121
|
+
const version = await this.capture(tx, workspaceId, pageId, {
|
|
122
|
+
kind: 'publish',
|
|
123
|
+
label,
|
|
124
|
+
authorId: principal.userId,
|
|
125
|
+
});
|
|
126
|
+
if (!version)
|
|
127
|
+
throw KernError.badRequest('There is nothing written to publish');
|
|
128
|
+
const [updated] = await tx
|
|
129
|
+
.update(pages)
|
|
130
|
+
.set({
|
|
131
|
+
publishedVersionId: version.id,
|
|
132
|
+
hasUnpublishedChanges: false,
|
|
133
|
+
updatedBy: principal.userId,
|
|
134
|
+
updatedAt: new Date(),
|
|
135
|
+
})
|
|
136
|
+
.where(and(eq(pages.workspaceId, workspaceId), eq(pages.id, pageId)))
|
|
137
|
+
.returning();
|
|
138
|
+
return updated;
|
|
139
|
+
},
|
|
140
|
+
/** Throw the draft away and go back to what readers can already see. */
|
|
141
|
+
async revert(tx, principal, workspaceId, pageId) {
|
|
142
|
+
const page = await access.pageRow(tx, workspaceId, pageId);
|
|
143
|
+
if (!page.publishedVersionId)
|
|
144
|
+
throw KernError.badRequest('This page has never been published, so there is nothing to go back to');
|
|
145
|
+
const published = await this.row(tx, workspaceId, page.publishedVersionId);
|
|
146
|
+
// The draft being discarded is kept, because "revert" should not be a way to lose an
|
|
147
|
+
// afternoon's writing with no way back.
|
|
148
|
+
await this.capture(tx, workspaceId, pageId, {
|
|
149
|
+
kind: 'auto',
|
|
150
|
+
label: null,
|
|
151
|
+
authorId: principal.userId,
|
|
152
|
+
});
|
|
153
|
+
await kernel.call('collab.document.replace', {
|
|
154
|
+
name: documentNameOf({ workspaceId, id: pageId }),
|
|
155
|
+
state: Buffer.from(published.state).toString('base64'),
|
|
156
|
+
});
|
|
157
|
+
const [updated] = await tx
|
|
158
|
+
.update(pages)
|
|
159
|
+
.set({ hasUnpublishedChanges: false, updatedBy: principal.userId, updatedAt: new Date() })
|
|
160
|
+
.where(and(eq(pages.workspaceId, workspaceId), eq(pages.id, pageId)))
|
|
161
|
+
.returning();
|
|
162
|
+
return updated;
|
|
163
|
+
},
|
|
164
|
+
/** When the newest version was taken, so an automatic one is not taken every keystroke. */
|
|
165
|
+
async lastCapturedAt(tx, workspaceId, pageId) {
|
|
166
|
+
const [row] = await tx
|
|
167
|
+
.select({ createdAt: pageVersions.createdAt })
|
|
168
|
+
.from(pageVersions)
|
|
169
|
+
.where(and(eq(pageVersions.workspaceId, workspaceId), eq(pageVersions.pageId, pageId)))
|
|
170
|
+
.orderBy(desc(pageVersions.id))
|
|
171
|
+
.limit(1);
|
|
172
|
+
return row?.createdAt ?? null;
|
|
173
|
+
},
|
|
174
|
+
/** The current state, for anything that needs the document without opening a socket. */
|
|
175
|
+
documentState(workspaceId, pageId) {
|
|
176
|
+
return kernel.call('collab.document.state', {
|
|
177
|
+
name: documentNameOf({ workspaceId, id: pageId }),
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=versions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.js","sourceRoot":"","sources":["../../../src/server/services/versions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS "mod_quire"."page_versions" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
3
|
+
"workspace_id" uuid NOT NULL,
|
|
4
|
+
"page_id" uuid NOT NULL,
|
|
5
|
+
"kind" text DEFAULT 'auto' NOT NULL,
|
|
6
|
+
"label" text,
|
|
7
|
+
"state" "bytea" NOT NULL,
|
|
8
|
+
"snapshot" "bytea",
|
|
9
|
+
"text" text DEFAULT '' NOT NULL,
|
|
10
|
+
"size" integer DEFAULT 0 NOT NULL,
|
|
11
|
+
"author_id" uuid,
|
|
12
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
13
|
+
);
|
|
14
|
+
--> statement-breakpoint
|
|
15
|
+
CREATE INDEX IF NOT EXISTS "page_versions_ws_page_idx" ON "mod_quire"."page_versions" USING btree ("workspace_id","page_id","created_at");--> statement-breakpoint
|
|
16
|
+
CREATE INDEX IF NOT EXISTS "page_versions_ws_created_idx" ON "mod_quire"."page_versions" USING btree ("workspace_id","created_at");--> statement-breakpoint
|
|
17
|
+
|
|
18
|
+
-- Row-level security, the same triple every tenant table gets. `force` matters because the table
|
|
19
|
+
-- owner would otherwise bypass the policy, and the owner is the role the service connects as.
|
|
20
|
+
ALTER TABLE "mod_quire"."page_versions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
|
21
|
+
ALTER TABLE "mod_quire"."page_versions" FORCE ROW LEVEL SECURITY;--> statement-breakpoint
|
|
22
|
+
DROP POLICY IF EXISTS "page_versions_ws_isolation" ON "mod_quire"."page_versions";--> statement-breakpoint
|
|
23
|
+
CREATE POLICY "page_versions_ws_isolation" ON "mod_quire"."page_versions"
|
|
24
|
+
USING (workspace_id::text = current_setting('app.workspace_id', true))
|
|
25
|
+
WITH CHECK (workspace_id::text = current_setting('app.workspace_id', true));
|