@kernhq/module-quire 0.13.0 → 0.14.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/models.d.ts +185 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +133 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/permissions.d.ts +14 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +82 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/properties.d.ts +4 -4
- package/dist/contract/router.d.ts +542 -15
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +155 -8
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +526 -15
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +166 -23
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/export/html.d.ts +67 -0
- package/dist/server/export/html.d.ts.map +1 -0
- package/dist/server/export/html.js +206 -0
- package/dist/server/export/html.js.map +1 -0
- package/dist/server/export/markdown.d.ts +51 -0
- package/dist/server/export/markdown.d.ts.map +1 -0
- package/dist/server/export/markdown.js +312 -0
- package/dist/server/export/markdown.js.map +1 -0
- package/dist/server/export/pdf.d.ts +20 -0
- package/dist/server/export/pdf.d.ts.map +1 -0
- package/dist/server/export/pdf.js +91 -0
- package/dist/server/export/pdf.js.map +1 -0
- package/dist/server/export/zip.d.ts +31 -0
- package/dist/server/export/zip.d.ts.map +1 -0
- package/dist/server/export/zip.js +158 -0
- package/dist/server/export/zip.js.map +1 -0
- package/dist/server/import/csv.d.ts +77 -0
- package/dist/server/import/csv.d.ts.map +1 -0
- package/dist/server/import/csv.js +263 -0
- package/dist/server/import/csv.js.map +1 -0
- package/dist/server/import/html.d.ts +52 -0
- package/dist/server/import/html.d.ts.map +1 -0
- package/dist/server/import/html.js +472 -0
- package/dist/server/import/html.js.map +1 -0
- package/dist/server/import/markdown.d.ts +63 -0
- package/dist/server/import/markdown.d.ts.map +1 -0
- package/dist/server/import/markdown.js +692 -0
- package/dist/server/import/markdown.js.map +1 -0
- package/dist/server/import/plan.d.ts +70 -0
- package/dist/server/import/plan.d.ts.map +1 -0
- package/dist/server/import/plan.js +761 -0
- package/dist/server/import/plan.js.map +1 -0
- package/dist/server/import/ydoc.d.ts +35 -0
- package/dist/server/import/ydoc.d.ts.map +1 -0
- package/dist/server/import/ydoc.js +91 -0
- package/dist/server/import/ydoc.js.map +1 -0
- package/dist/server/import/zip.d.ts +63 -0
- package/dist/server/import/zip.d.ts.map +1 -0
- package/dist/server/import/zip.js +308 -0
- package/dist/server/import/zip.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema.d.ts +445 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +146 -0
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/databases.d.ts +5 -5
- package/dist/server/services/export.d.ts +176 -0
- package/dist/server/services/export.d.ts.map +1 -0
- package/dist/server/services/export.js +822 -0
- package/dist/server/services/export.js.map +1 -0
- package/dist/server/services/import.d.ts +109 -0
- package/dist/server/services/import.d.ts.map +1 -0
- package/dist/server/services/import.js +570 -0
- package/dist/server/services/import.js.map +1 -0
- package/dist/server/services/index.d.ts +28 -1
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +62 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/publications.d.ts +34 -1
- package/dist/server/services/publications.d.ts.map +1 -1
- package/dist/server/services/publications.js +62 -3
- package/dist/server/services/publications.js.map +1 -1
- package/dist/server/services/versions.d.ts +1 -1
- package/migrations/0010_transfers.sql +154 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/client/components/ExportDialog.svelte +685 -0
- package/src/client/components/ImportDialog.svelte +702 -0
- package/src/client/components/ImportReport.svelte +310 -0
- package/src/client/components/SidebarSpaces.svelte +79 -0
- package/src/client/i18n.ts +614 -0
- package/src/client/index.ts +32 -0
- package/src/client/mock.ts +318 -0
- package/src/client/module.ts +36 -0
- package/src/client/pages/PageView.svelte +36 -0
- package/src/client/pages/TransfersPage.svelte +570 -0
- package/src/client/permissions.ts +11 -0
- package/src/client/query.ts +23 -0
- package/src/client/transfers.ts +142 -0
- package/src/contract/models.ts +152 -0
- package/src/contract/permissions.ts +84 -0
- package/src/contract/router.ts +170 -7
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import { KernError, uuidv7 } from '@kernhq/kernel';
|
|
2
|
+
import { and, asc, desc, eq, inArray, isNull, lt } from 'drizzle-orm';
|
|
3
|
+
import { initialRank, rankBetween } from '../../client/rank.js';
|
|
4
|
+
import { MODULE_ID } from '../../contract/index.js';
|
|
5
|
+
import { planImport } from '../import/plan.js';
|
|
6
|
+
import { pageDocToYState } from '../import/ydoc.js';
|
|
7
|
+
import { readZip } from '../import/zip.js';
|
|
8
|
+
import { textFromPageDoc } from '../render.js';
|
|
9
|
+
import { importJobs, pages, pageVersions, spaces } from '../schema.js';
|
|
10
|
+
import { documentNameOf } from './pages.js';
|
|
11
|
+
/**
|
|
12
|
+
* How long a job may sit un-finished before it is given up on.
|
|
13
|
+
*
|
|
14
|
+
* The same hole as an export's, and worse here on both counts. `retryLimit: 0` means a worker
|
|
15
|
+
* killed mid-import is never re-tried, so the row is `running` from the moment the process dies —
|
|
16
|
+
* and decision 5 above means nothing ever deletes it either, so unlike an export it does not even
|
|
17
|
+
* age out of the list. A row that says "running" a month later is a screen telling somebody their
|
|
18
|
+
* pages are still on their way.
|
|
19
|
+
*
|
|
20
|
+
* Two hours is the job's whole budget with room to spare: `quireJobs` gives `import`
|
|
21
|
+
* `expireInSeconds: 3600` and one attempt. Nothing legitimate is still running after it, and a job
|
|
22
|
+
* that is has already been abandoned by the queue.
|
|
23
|
+
*/
|
|
24
|
+
const ABANDONED_AFTER_MS = 2 * 60 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* What a job that lost its worker says.
|
|
27
|
+
*
|
|
28
|
+
* It leads with the thing somebody wants to know before anything else, which is the same thing the
|
|
29
|
+
* dialog says about every other import failure: the write is one transaction, so an import that did
|
|
30
|
+
* not finish did not half-finish.
|
|
31
|
+
*/
|
|
32
|
+
const ABANDONED = 'This import stopped before it finished and was given up on after two hours. The worker running ' +
|
|
33
|
+
'it went away — usually a restart, or an archive too big for the memory it had. Nothing was ' +
|
|
34
|
+
'written: an import is one transaction, so the space is exactly as it was. Upload it again.';
|
|
35
|
+
/** Neither `done` nor `failed`, and old enough that nothing is still going to happen to it. */
|
|
36
|
+
const isAbandoned = (row) => (row.state === 'queued' || row.state === 'running') &&
|
|
37
|
+
row.createdAt.getTime() < Date.now() - ABANDONED_AFTER_MS;
|
|
38
|
+
/**
|
|
39
|
+
* The largest archive one job will read.
|
|
40
|
+
*
|
|
41
|
+
* The plan is built in memory before anything is written — see the note at the top — so the limit is
|
|
42
|
+
* about what one worker may hold rather than about what a person may upload. The zip reader has its
|
|
43
|
+
* own, stricter limit on what an archive *unpacks* to.
|
|
44
|
+
*/
|
|
45
|
+
const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
|
|
46
|
+
/** Rows written at a time, so a four-thousand-page import is not four thousand round trips. */
|
|
47
|
+
const BATCH = 200;
|
|
48
|
+
const countsOf = (value) => {
|
|
49
|
+
const raw = (value ?? {});
|
|
50
|
+
const n = (v) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.trunc(v) : 0);
|
|
51
|
+
return { total: n(raw.total), done: n(raw.done), skipped: n(raw.skipped), failed: n(raw.failed) };
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The stored report, parsed rather than cast.
|
|
55
|
+
*
|
|
56
|
+
* The column is jsonb written by a previous release of this module, so a cast promises fields that
|
|
57
|
+
* may not be there. Anything unreadable is dropped rather than thrown: a report is a record of what
|
|
58
|
+
* happened, and failing to *show* one would be a worse answer than showing what is legible.
|
|
59
|
+
*/
|
|
60
|
+
function reportOf(value) {
|
|
61
|
+
if (!Array.isArray(value))
|
|
62
|
+
return [];
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const raw of value) {
|
|
65
|
+
if (typeof raw !== 'object' || raw === null)
|
|
66
|
+
continue;
|
|
67
|
+
const entry = raw;
|
|
68
|
+
if (typeof entry.path !== 'string')
|
|
69
|
+
continue;
|
|
70
|
+
if (entry.outcome !== 'imported' && entry.outcome !== 'skipped' && entry.outcome !== 'failed')
|
|
71
|
+
continue;
|
|
72
|
+
out.push({
|
|
73
|
+
path: entry.path,
|
|
74
|
+
outcome: entry.outcome,
|
|
75
|
+
pageId: typeof entry.pageId === 'string' ? entry.pageId : null,
|
|
76
|
+
reason: typeof entry.reason === 'string' ? entry.reason : null,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
export function toImportJob(row) {
|
|
82
|
+
return {
|
|
83
|
+
id: row.id,
|
|
84
|
+
workspaceId: row.workspaceId,
|
|
85
|
+
requestedBy: row.requestedBy,
|
|
86
|
+
source: row.source,
|
|
87
|
+
targetId: row.targetId,
|
|
88
|
+
sourceFileId: row.sourceFileId,
|
|
89
|
+
state: row.state,
|
|
90
|
+
error: row.error,
|
|
91
|
+
counts: countsOf(row.counts),
|
|
92
|
+
report: reportOf(row.report),
|
|
93
|
+
createdAt: row.createdAt.toISOString(),
|
|
94
|
+
finishedAt: row.finishedAt?.toISOString() ?? null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Counters over report rows, which is what makes `total` equal to the number of rows in it. */
|
|
98
|
+
export function countReport(report) {
|
|
99
|
+
return {
|
|
100
|
+
total: report.length,
|
|
101
|
+
done: report.filter((entry) => entry.outcome === 'imported').length,
|
|
102
|
+
skipped: report.filter((entry) => entry.outcome === 'skipped').length,
|
|
103
|
+
failed: report.filter((entry) => entry.outcome === 'failed').length,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function quireImport(kernel, access, databases) {
|
|
107
|
+
/** The row, or `notFound`. Fenced to the person who asked for it, exactly as an export is. */
|
|
108
|
+
async function own(tx, workspaceId, jobId, principal) {
|
|
109
|
+
const [row] = await tx
|
|
110
|
+
.select()
|
|
111
|
+
.from(importJobs)
|
|
112
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.id, jobId)))
|
|
113
|
+
.limit(1);
|
|
114
|
+
if (!row)
|
|
115
|
+
throw KernError.notFound('Import');
|
|
116
|
+
/*
|
|
117
|
+
* NOT_FOUND rather than FORBIDDEN, the same as an export and for a related reason: a report names
|
|
118
|
+
* every file in somebody's archive, which is a listing of what they had somewhere else. "That one
|
|
119
|
+
* is not yours" would confirm that a colleague imported something, and when.
|
|
120
|
+
*/
|
|
121
|
+
const mine = principal.instanceAdmin || principal.kind === 'service' || row.requestedBy === principal.userId;
|
|
122
|
+
if (!mine)
|
|
123
|
+
throw KernError.notFound('Import');
|
|
124
|
+
return row;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Tell the one person whose job this is that it moved — and nobody else.
|
|
128
|
+
*
|
|
129
|
+
* `kernel.realtime.change` publishes to the **workspace** channel, which the gateway joins every
|
|
130
|
+
* socket to at `hello` with no per-message filter — right for a page, wrong for a transfer. `own()`
|
|
131
|
+
* answers NOT_FOUND rather than FORBIDDEN so that an id belonging to somebody else's import
|
|
132
|
+
* confirms nothing, and then the broadcast told the whole workspace that a job with that id
|
|
133
|
+
* changed, and when. `services/export.ts` carries the long version of this note.
|
|
134
|
+
*/
|
|
135
|
+
async function announce(workspaceId, requestedBy, jobId) {
|
|
136
|
+
await kernel.realtime
|
|
137
|
+
.toUser(requestedBy, {
|
|
138
|
+
t: 'change',
|
|
139
|
+
workspaceId: workspaceId,
|
|
140
|
+
change: { module: 'quire', entity: 'import', id: jobId, op: 'updated' },
|
|
141
|
+
})
|
|
142
|
+
.catch(() => undefined);
|
|
143
|
+
}
|
|
144
|
+
async function patch(workspaceId, requestedBy, jobId, values) {
|
|
145
|
+
await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
146
|
+
await tx
|
|
147
|
+
.update(importJobs)
|
|
148
|
+
.set(values)
|
|
149
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.id, jobId)));
|
|
150
|
+
});
|
|
151
|
+
await announce(workspaceId, requestedBy, jobId);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The uploaded archive's bytes, checked against the tenant that is asking for them.
|
|
155
|
+
*
|
|
156
|
+
* The workspace is **compared here rather than trusted**. `core.files.get` answers a service
|
|
157
|
+
* principal without a membership check — that is what makes it callable from a module at all — and
|
|
158
|
+
* `fileId` came from the request, so this comparison is the only thing stopping one workspace
|
|
159
|
+
* naming another's upload and having Quire unpack it into a space of its own.
|
|
160
|
+
*/
|
|
161
|
+
async function archiveBytes(workspaceId, fileId) {
|
|
162
|
+
const file = await kernel
|
|
163
|
+
.call('core.files.get', { id: fileId })
|
|
164
|
+
.catch(() => null);
|
|
165
|
+
if (!file || file.workspaceId !== workspaceId)
|
|
166
|
+
throw KernError.notFound('File');
|
|
167
|
+
if (file.status !== 'ready' || !file.key)
|
|
168
|
+
throw KernError.badRequest('That upload has not finished. Wait for it to appear, then import it.');
|
|
169
|
+
if (file.size > MAX_ARCHIVE_BYTES)
|
|
170
|
+
throw KernError.badRequest(`That archive is ${Math.round(file.size / 1_048_576)} MB, and one import may carry ` +
|
|
171
|
+
`${MAX_ARCHIVE_BYTES / 1_048_576} MB. Export it in smaller pieces — one space at a time.`);
|
|
172
|
+
const object = await kernel.storage.get(file.key);
|
|
173
|
+
const chunks = [];
|
|
174
|
+
for await (const chunk of object.body)
|
|
175
|
+
chunks.push(Buffer.from(chunk));
|
|
176
|
+
return Buffer.concat(chunks);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* May this person still write here?
|
|
180
|
+
*
|
|
181
|
+
* Both keys, and both at space scope. `quire.page.import` is the one that says the bulk shape is
|
|
182
|
+
* available at all; `quire.page.create` is "a space you may write to", which is a different
|
|
183
|
+
* question a workspace can answer differently — a space closed for editing during a freeze is
|
|
184
|
+
* closed to an import as well, and reading only the import key would walk straight past that.
|
|
185
|
+
*/
|
|
186
|
+
async function requireWritable(principal, workspaceId, spaceId) {
|
|
187
|
+
await access.requireSpace(principal, 'quire.page.import', workspaceId, spaceId);
|
|
188
|
+
await access.requireSpace(principal, 'quire.page.create', workspaceId, spaceId);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Write the plan.
|
|
192
|
+
*
|
|
193
|
+
* One transaction, in an order that is not arbitrary: pages before databases, because a database
|
|
194
|
+
* hangs on a page; properties before rows, because a row's cells are keyed by the properties'
|
|
195
|
+
* *actual* keys; and documents last, because a document is the only part that leaves the database.
|
|
196
|
+
*/
|
|
197
|
+
async function write(tx, principal, workspaceId, spaceId, plan) {
|
|
198
|
+
/*
|
|
199
|
+
* Imported pages land after whatever the space already has, so an import never reorders the tree
|
|
200
|
+
* somebody built. The rank is chained from the last existing root rather than minted per page:
|
|
201
|
+
* `rankBetween(last, null)` is only strictly increasing if each call sees the previous answer.
|
|
202
|
+
*/
|
|
203
|
+
const [lastRoot] = await tx
|
|
204
|
+
.select({ position: pages.position })
|
|
205
|
+
.from(pages)
|
|
206
|
+
.where(and(eq(pages.workspaceId, workspaceId), eq(pages.spaceId, spaceId), isNull(pages.parentId), isNull(pages.deletedAt)))
|
|
207
|
+
.orderBy(desc(pages.position))
|
|
208
|
+
.limit(1);
|
|
209
|
+
const tail = new Map([[null, lastRoot?.position ?? null]]);
|
|
210
|
+
const nextPosition = (parentId) => {
|
|
211
|
+
const previous = tail.get(parentId) ?? null;
|
|
212
|
+
const position = previous === null ? initialRank() : rankBetween(previous, null);
|
|
213
|
+
tail.set(parentId, position);
|
|
214
|
+
return position;
|
|
215
|
+
};
|
|
216
|
+
const now = new Date();
|
|
217
|
+
const rows = plan.pages.map((page) => ({
|
|
218
|
+
id: page.id,
|
|
219
|
+
workspaceId,
|
|
220
|
+
spaceId,
|
|
221
|
+
parentId: page.parentId,
|
|
222
|
+
position: nextPosition(page.parentId),
|
|
223
|
+
kind: 'page',
|
|
224
|
+
title: page.title.slice(0, 500),
|
|
225
|
+
text: textFromPageDoc(page.doc),
|
|
226
|
+
createdBy: principal.userId,
|
|
227
|
+
updatedBy: principal.userId,
|
|
228
|
+
createdAt: now,
|
|
229
|
+
updatedAt: now,
|
|
230
|
+
}));
|
|
231
|
+
for (let at = 0; at < rows.length; at += BATCH)
|
|
232
|
+
await tx.insert(pages).values(rows.slice(at, at + BATCH));
|
|
233
|
+
const documents = plan.pages
|
|
234
|
+
.filter((page) => (page.doc.content ?? []).length > 0)
|
|
235
|
+
.map((page) => ({ pageId: page.id, doc: page.doc }));
|
|
236
|
+
for (const planned of plan.databases) {
|
|
237
|
+
const database = await databases.create(tx, principal, workspaceId, {
|
|
238
|
+
spaceId,
|
|
239
|
+
pageId: planned.hostPageId,
|
|
240
|
+
name: planned.name.slice(0, 200),
|
|
241
|
+
inline: false,
|
|
242
|
+
});
|
|
243
|
+
/*
|
|
244
|
+
* `databases.create` seeds a text column called `Name`, which is right for a database somebody
|
|
245
|
+
* creates by hand and wrong for one that arrives with its own columns: the CSV's first column
|
|
246
|
+
* is the row *title*, so the seeded one would sit beside the title, empty, in every view.
|
|
247
|
+
*/
|
|
248
|
+
for (const seeded of database.properties)
|
|
249
|
+
await databases.removeProperty(tx, workspaceId, seeded.id);
|
|
250
|
+
const keys = [];
|
|
251
|
+
for (const column of planned.columns) {
|
|
252
|
+
const property = await databases.addProperty(tx, workspaceId, database.id, {
|
|
253
|
+
name: column.name.slice(0, 120),
|
|
254
|
+
type: column.type,
|
|
255
|
+
config: column.config,
|
|
256
|
+
});
|
|
257
|
+
keys.push(property.key);
|
|
258
|
+
}
|
|
259
|
+
const rowRows = planned.rows.map((row) => {
|
|
260
|
+
const props = {};
|
|
261
|
+
// Zipped against the keys the database actually minted — never against a key guessed here.
|
|
262
|
+
row.values.forEach((value, index) => {
|
|
263
|
+
const key = keys[index];
|
|
264
|
+
if (key !== undefined && value !== null)
|
|
265
|
+
props[key] = value;
|
|
266
|
+
});
|
|
267
|
+
return {
|
|
268
|
+
id: row.id,
|
|
269
|
+
workspaceId,
|
|
270
|
+
spaceId,
|
|
271
|
+
parentId: planned.hostPageId,
|
|
272
|
+
position: nextPosition(planned.hostPageId),
|
|
273
|
+
kind: 'page',
|
|
274
|
+
title: row.title.slice(0, 500),
|
|
275
|
+
text: textFromPageDoc(row.doc),
|
|
276
|
+
databaseId: database.id,
|
|
277
|
+
props,
|
|
278
|
+
createdBy: principal.userId,
|
|
279
|
+
updatedBy: principal.userId,
|
|
280
|
+
createdAt: now,
|
|
281
|
+
updatedAt: now,
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
for (let at = 0; at < rowRows.length; at += BATCH)
|
|
285
|
+
await tx.insert(pages).values(rowRows.slice(at, at + BATCH));
|
|
286
|
+
for (const row of planned.rows)
|
|
287
|
+
if ((row.doc.content ?? []).length > 0)
|
|
288
|
+
documents.push({ pageId: row.id, doc: row.doc });
|
|
289
|
+
}
|
|
290
|
+
/*
|
|
291
|
+
* The document, then the version — in that order and both inside the transaction.
|
|
292
|
+
*
|
|
293
|
+
* `versions.capture` is not used here on purpose: it reads the state back from the collab service
|
|
294
|
+
* and returns *null* when it cannot, so an import would quietly produce pages with no history at
|
|
295
|
+
* all. The bytes are already in hand, so the row is written from them directly, and a collab
|
|
296
|
+
* service that will not take the document fails the import instead.
|
|
297
|
+
*/
|
|
298
|
+
for (const { pageId, doc } of documents) {
|
|
299
|
+
const state = pageDocToYState(doc);
|
|
300
|
+
await kernel.call('collab.document.replace', {
|
|
301
|
+
name: documentNameOf({ workspaceId, id: pageId }),
|
|
302
|
+
state: state.toString('base64'),
|
|
303
|
+
});
|
|
304
|
+
await tx.insert(pageVersions).values({
|
|
305
|
+
id: uuidv7(),
|
|
306
|
+
workspaceId,
|
|
307
|
+
pageId,
|
|
308
|
+
kind: 'import',
|
|
309
|
+
label: null,
|
|
310
|
+
state,
|
|
311
|
+
// `Y.encodeSnapshot` needs a document somebody has edited; a version with no snapshot simply
|
|
312
|
+
// cannot be diffed against another, which is true of the first version of every page.
|
|
313
|
+
snapshot: null,
|
|
314
|
+
text: textFromPageDoc(doc),
|
|
315
|
+
size: state.length,
|
|
316
|
+
authorId: principal.userId ?? null,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Put the imported pages in the workspace search index.
|
|
322
|
+
*
|
|
323
|
+
* Best effort, after the transaction: a page that is not findable until somebody edits it is a real
|
|
324
|
+
* defect — three hundred pages nobody can search for is most of what an import is *for* — and a
|
|
325
|
+
* search service that is briefly down must not undo an import that has already committed.
|
|
326
|
+
*
|
|
327
|
+
* The rule about *which* pages are indexed lives in `server/index.ts`'s `searchDocument`, and is
|
|
328
|
+
* repeated rather than imported because importing it would make this file and the module definition
|
|
329
|
+
* depend on each other. There is exactly one rule to keep in step: only a space whose visibility is
|
|
330
|
+
* `open` is indexed at all, because `SearchDocument.acl` cannot yet express who may read a
|
|
331
|
+
* restricted space.
|
|
332
|
+
*/
|
|
333
|
+
async function indexPages(workspaceId, spaceId, plan) {
|
|
334
|
+
try {
|
|
335
|
+
const found = await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
336
|
+
const [space] = await tx
|
|
337
|
+
.select({ key: spaces.key, visibility: spaces.visibility })
|
|
338
|
+
.from(spaces)
|
|
339
|
+
.where(and(eq(spaces.workspaceId, workspaceId), eq(spaces.id, spaceId)))
|
|
340
|
+
.limit(1);
|
|
341
|
+
if (space?.visibility !== 'open')
|
|
342
|
+
return null;
|
|
343
|
+
const rows = await tx
|
|
344
|
+
.select({
|
|
345
|
+
id: pages.id,
|
|
346
|
+
title: pages.title,
|
|
347
|
+
text: pages.text,
|
|
348
|
+
kind: pages.kind,
|
|
349
|
+
updatedAt: pages.updatedAt,
|
|
350
|
+
})
|
|
351
|
+
.from(pages)
|
|
352
|
+
.where(and(eq(pages.workspaceId, workspaceId), eq(pages.spaceId, spaceId)))
|
|
353
|
+
.orderBy(asc(pages.id));
|
|
354
|
+
return { space, rows };
|
|
355
|
+
});
|
|
356
|
+
if (!found)
|
|
357
|
+
return;
|
|
358
|
+
const imported = new Set([
|
|
359
|
+
...plan.pages.map((page) => page.id),
|
|
360
|
+
...plan.databases.flatMap((database) => database.rows.map((row) => row.id)),
|
|
361
|
+
]);
|
|
362
|
+
const documents = found.rows
|
|
363
|
+
.filter((row) => imported.has(row.id))
|
|
364
|
+
.map((row) => ({
|
|
365
|
+
workspaceId: workspaceId,
|
|
366
|
+
object: { module: MODULE_ID, type: 'page', id: row.id },
|
|
367
|
+
title: row.title || 'Untitled',
|
|
368
|
+
body: row.text,
|
|
369
|
+
url: `/quire/${found.space.key}/${row.id}`,
|
|
370
|
+
icon: 'file-text',
|
|
371
|
+
acl: null,
|
|
372
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
373
|
+
attributes: { spaceId, kind: row.kind },
|
|
374
|
+
}));
|
|
375
|
+
for (let at = 0; at < documents.length; at += BATCH)
|
|
376
|
+
await kernel.call('core.search.index', { documents: documents.slice(at, at + BATCH) });
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
kernel.log.warn({ err: String(err), spaceId }, 'quire: imported pages could not be indexed');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
toImportJob,
|
|
384
|
+
/**
|
|
385
|
+
* Record the request and hand it to a worker.
|
|
386
|
+
*
|
|
387
|
+
* Nothing is read here beyond the file's own header: a zip of thousands of files is unbounded
|
|
388
|
+
* work, so the request answers with a row somebody can watch. What *is* done here is every check
|
|
389
|
+
* that can be made cheaply and would otherwise fail a minute later in a worker nobody is
|
|
390
|
+
* watching — the space exists, the caller may write to it, the file is this workspace's and has
|
|
391
|
+
* finished uploading.
|
|
392
|
+
*/
|
|
393
|
+
async start(tx, principal, workspaceId, input) {
|
|
394
|
+
const userId = principal.userId;
|
|
395
|
+
if (!userId)
|
|
396
|
+
throw new KernError('FORBIDDEN', 'An import belongs to a person, and this caller is a service');
|
|
397
|
+
await access.spaceRow(tx, workspaceId, input.spaceId);
|
|
398
|
+
await requireWritable(principal, workspaceId, input.spaceId);
|
|
399
|
+
const file = await kernel
|
|
400
|
+
.call('core.files.get', { id: input.fileId })
|
|
401
|
+
.catch(() => null);
|
|
402
|
+
if (!file || file.workspaceId !== workspaceId)
|
|
403
|
+
throw KernError.notFound('File');
|
|
404
|
+
if (file.status !== 'ready')
|
|
405
|
+
throw KernError.badRequest('That upload has not finished. Wait for it to appear, then import it.');
|
|
406
|
+
const [row] = await tx
|
|
407
|
+
.insert(importJobs)
|
|
408
|
+
.values({
|
|
409
|
+
id: uuidv7(),
|
|
410
|
+
workspaceId,
|
|
411
|
+
requestedBy: userId,
|
|
412
|
+
source: input.source,
|
|
413
|
+
targetId: input.spaceId,
|
|
414
|
+
sourceFileId: input.fileId,
|
|
415
|
+
state: 'queued',
|
|
416
|
+
counts: { total: 0, done: 0, skipped: 0, failed: 0 },
|
|
417
|
+
report: [],
|
|
418
|
+
})
|
|
419
|
+
.returning();
|
|
420
|
+
if (!row)
|
|
421
|
+
throw new KernError('INTERNAL', 'The import could not be recorded');
|
|
422
|
+
return row;
|
|
423
|
+
},
|
|
424
|
+
/**
|
|
425
|
+
* One job, and the one place a *watched* job can give up on itself.
|
|
426
|
+
*
|
|
427
|
+
* The dialog polls this and never the list, so `reap` — which runs beside the list — would never
|
|
428
|
+
* reach the row somebody is actually looking at. One row rather than a workspace-wide update,
|
|
429
|
+
* because this is on a path that runs every second and a half while a job is moving.
|
|
430
|
+
*/
|
|
431
|
+
async get(tx, workspaceId, jobId, principal) {
|
|
432
|
+
const row = await own(tx, workspaceId, jobId, principal);
|
|
433
|
+
if (!isAbandoned(row))
|
|
434
|
+
return row;
|
|
435
|
+
const [failed] = await tx
|
|
436
|
+
.update(importJobs)
|
|
437
|
+
.set({ state: 'failed', error: ABANDONED, finishedAt: new Date() })
|
|
438
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.id, jobId)))
|
|
439
|
+
.returning();
|
|
440
|
+
return failed ?? row;
|
|
441
|
+
},
|
|
442
|
+
/**
|
|
443
|
+
* Fail this workspace's abandoned jobs, so a dead one stops reading as work in progress.
|
|
444
|
+
*
|
|
445
|
+
* This is not the sweep decision 5 above argues against, and the difference is the whole point:
|
|
446
|
+
* a sweep would *delete* the record of a dangerous operation, and this writes down that it did
|
|
447
|
+
* not happen. The row stays, with a reason on it.
|
|
448
|
+
*
|
|
449
|
+
* Called from `imports.start` and `imports.list`, which is the same opportunistic shape
|
|
450
|
+
* `exports.sweep` explains: every tenant table is under FORCE row-level security keyed on
|
|
451
|
+
* `app.workspace_id`, and Quire keeps no `workspaces` table, so a cron job has nothing to
|
|
452
|
+
* enumerate. The limit is the same one and is worth stating: a workspace nobody comes back to
|
|
453
|
+
* keeps its lie until somebody looks. The transfers list polls every two seconds while anything
|
|
454
|
+
* is running, so in practice the screen being lied to is the one that ends it.
|
|
455
|
+
*/
|
|
456
|
+
async reap(tx, workspaceId) {
|
|
457
|
+
const abandoned = await tx
|
|
458
|
+
.update(importJobs)
|
|
459
|
+
.set({ state: 'failed', error: ABANDONED, finishedAt: new Date() })
|
|
460
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), inArray(importJobs.state, ['queued', 'running']), lt(importJobs.createdAt, new Date(Date.now() - ABANDONED_AFTER_MS))))
|
|
461
|
+
.returning({ id: importJobs.id });
|
|
462
|
+
return abandoned.length;
|
|
463
|
+
},
|
|
464
|
+
/** This person's own imports, newest first. Somebody else's are not listed and cannot be. */
|
|
465
|
+
list(tx, workspaceId, principal, limit) {
|
|
466
|
+
const userId = principal.userId;
|
|
467
|
+
if (!userId)
|
|
468
|
+
return Promise.resolve([]);
|
|
469
|
+
return tx
|
|
470
|
+
.select()
|
|
471
|
+
.from(importJobs)
|
|
472
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.requestedBy, userId)))
|
|
473
|
+
.orderBy(desc(importJobs.createdAt))
|
|
474
|
+
.limit(limit);
|
|
475
|
+
},
|
|
476
|
+
/**
|
|
477
|
+
* Mark a job failed without having run it.
|
|
478
|
+
*
|
|
479
|
+
* One caller: the router, when the queue refuses the job it has just recorded. Without it the row
|
|
480
|
+
* sits `queued` for ever and reads as work in progress, which is the worst of the three states to
|
|
481
|
+
* be wrong about — worse here than for an export, because somebody watching it is waiting to find
|
|
482
|
+
* out whether their pages arrived.
|
|
483
|
+
*/
|
|
484
|
+
async fail(tx, workspaceId, jobId, reason) {
|
|
485
|
+
await tx
|
|
486
|
+
.update(importJobs)
|
|
487
|
+
.set({ state: 'failed', error: reason.slice(0, 2000), finishedAt: new Date() })
|
|
488
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.id, jobId)));
|
|
489
|
+
},
|
|
490
|
+
/**
|
|
491
|
+
* The job: read the archive, build the plan, write it once.
|
|
492
|
+
*
|
|
493
|
+
* Everything before the transaction holds nothing — fetching a few hundred megabytes from storage
|
|
494
|
+
* and parsing thousands of files must not do so with a connection open. Everything inside it is
|
|
495
|
+
* one act, so the space either gains the whole import or nothing at all.
|
|
496
|
+
*/
|
|
497
|
+
async run(workspaceId, jobId) {
|
|
498
|
+
/*
|
|
499
|
+
* The claim is **one conditional UPDATE**, so exactly one attempt can ever hold this row.
|
|
500
|
+
*
|
|
501
|
+
* `select` then `update` is a lost update under READ COMMITTED — two workers both read
|
|
502
|
+
* `queued`, both write `running`, and both run — and here that means every page in the archive
|
|
503
|
+
* written twice. `where state = 'queued'` closes it: Postgres re-evaluates the predicate after
|
|
504
|
+
* the other transaction commits, so the loser matches no row. `done` and `failed` are excluded
|
|
505
|
+
* by the same clause, which is what makes a retry of a finished job a no-op. See
|
|
506
|
+
* `services/export.ts` for what this costs — a row whose worker died stays `running` until
|
|
507
|
+
* `reap` gives up on it, rather than being re-run by the next retry.
|
|
508
|
+
*/
|
|
509
|
+
const job = await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
510
|
+
const [row] = await tx
|
|
511
|
+
.update(importJobs)
|
|
512
|
+
.set({ state: 'running', error: null })
|
|
513
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.id, jobId), eq(importJobs.state, 'queued')))
|
|
514
|
+
.returning();
|
|
515
|
+
return row ?? null;
|
|
516
|
+
});
|
|
517
|
+
if (!job)
|
|
518
|
+
return;
|
|
519
|
+
await announce(workspaceId, job.requestedBy, jobId);
|
|
520
|
+
try {
|
|
521
|
+
/*
|
|
522
|
+
* The import runs as the person who asked for it, which is the whole permission model here.
|
|
523
|
+
* A job has no principal of its own, so it fetches theirs — and when it cannot, it fails
|
|
524
|
+
* rather than falling back to something more permissive.
|
|
525
|
+
*/
|
|
526
|
+
const principal = await kernel
|
|
527
|
+
.call('core.users.principal', { userId: job.requestedBy })
|
|
528
|
+
.catch(() => null);
|
|
529
|
+
if (!principal)
|
|
530
|
+
throw new KernError('INTERNAL', 'The person who asked for this import could not be identified');
|
|
531
|
+
// Asked again, now, at the moment of writing — see the note at the top of the file.
|
|
532
|
+
await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
533
|
+
await access.spaceRow(tx, workspaceId, job.targetId);
|
|
534
|
+
await requireWritable(principal, workspaceId, job.targetId);
|
|
535
|
+
});
|
|
536
|
+
const bytes = await archiveBytes(workspaceId, job.sourceFileId);
|
|
537
|
+
const plan = planImport(readZip(bytes), job.source);
|
|
538
|
+
const counts = countReport(plan.report);
|
|
539
|
+
await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
540
|
+
await write(tx, principal, workspaceId, job.targetId, plan);
|
|
541
|
+
await tx
|
|
542
|
+
.update(importJobs)
|
|
543
|
+
.set({ state: 'done', counts, report: plan.report, finishedAt: new Date() })
|
|
544
|
+
.where(and(eq(importJobs.workspaceId, workspaceId), eq(importJobs.id, jobId)));
|
|
545
|
+
}, { userId: principal.userId });
|
|
546
|
+
await indexPages(workspaceId, job.targetId, plan);
|
|
547
|
+
await announce(workspaceId, job.requestedBy, jobId);
|
|
548
|
+
/*
|
|
549
|
+
* The *space* still goes to the whole workspace, and that is the line between the two.
|
|
550
|
+
* Pages arriving in a shared space is news for everyone whose sidebar shows that space — it
|
|
551
|
+
* is the same change a `pages.create` announces. Which job put them there is not.
|
|
552
|
+
*/
|
|
553
|
+
await kernel.realtime
|
|
554
|
+
.change(workspaceId, { module: 'quire', entity: 'space', id: job.targetId, op: 'updated' })
|
|
555
|
+
.catch(() => undefined);
|
|
556
|
+
kernel.log.info({ jobId, workspaceId, source: job.source, ...counts }, 'quire: import finished');
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
560
|
+
await patch(workspaceId, job.requestedBy, jobId, {
|
|
561
|
+
state: 'failed',
|
|
562
|
+
error: message.slice(0, 2000),
|
|
563
|
+
finishedAt: new Date(),
|
|
564
|
+
});
|
|
565
|
+
kernel.log.warn({ err: message, jobId, workspaceId }, 'quire: import failed');
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
//# sourceMappingURL=import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.js","sourceRoot":"","sources":["../../../src/server/services/import.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { Kernel } from '@kernhq/kernel';
|
|
1
|
+
import type { JobDef, Kernel } from '@kernhq/kernel';
|
|
2
2
|
import { quireAccess } from './access.js';
|
|
3
3
|
import { quireComments } from './comments.js';
|
|
4
4
|
import { quireDatabases } from './databases.js';
|
|
5
|
+
import { quireExport } from './export.js';
|
|
6
|
+
import { quireImport } from './import.js';
|
|
5
7
|
import { quireOrganisation } from './organisation.js';
|
|
6
8
|
import { quirePages } from './pages.js';
|
|
7
9
|
import { quirePublications } from './publications.js';
|
|
@@ -16,15 +18,40 @@ export interface QuireServices {
|
|
|
16
18
|
databases: ReturnType<typeof quireDatabases>;
|
|
17
19
|
organisation: ReturnType<typeof quireOrganisation>;
|
|
18
20
|
publications: ReturnType<typeof quirePublications>;
|
|
21
|
+
exports: ReturnType<typeof quireExport>;
|
|
22
|
+
imports: ReturnType<typeof quireImport>;
|
|
19
23
|
}
|
|
20
24
|
/** One set of services per kernel: they are stateless, and rebuilding them per request is waste. */
|
|
21
25
|
export declare function quireServices(kernel: Kernel): QuireServices;
|
|
26
|
+
/**
|
|
27
|
+
* The background work this module hands to a worker process, gathered here rather than in
|
|
28
|
+
* `index.ts`.
|
|
29
|
+
*
|
|
30
|
+
* One line in the module definition, so adding a job is an edit to this file and not to the file
|
|
31
|
+
* every other part of the module is also editing. The names reach pg-boss as `quire.<name>` — the
|
|
32
|
+
* kernel prefixes them — and only a process started with a worker role runs them; an API process
|
|
33
|
+
* registers nothing and merely sends.
|
|
34
|
+
*
|
|
35
|
+
* `retryLimit` is deliberately small. An export that fails for a reason of its own — Gotenberg is
|
|
36
|
+
* not running, a page will not render — records that reason on its row and returns *successfully*,
|
|
37
|
+
* because it has finished: it failed, and it said so, and repeating it would only write the same
|
|
38
|
+
* sentence three more times. A throw out of the handler means something underneath went wrong
|
|
39
|
+
* (the database, the broker), which is the case a retry is for.
|
|
40
|
+
*/
|
|
41
|
+
export declare const quireJobs: JobDef<{
|
|
42
|
+
workspaceId: string;
|
|
43
|
+
jobId: string;
|
|
44
|
+
}>[];
|
|
22
45
|
export * from './access.js';
|
|
23
46
|
export * from './comments.js';
|
|
24
47
|
export * from './databases.js';
|
|
48
|
+
export * from './export.js';
|
|
49
|
+
export * from './import.js';
|
|
25
50
|
export * from './organisation.js';
|
|
26
51
|
export * from './pages.js';
|
|
27
52
|
export * from './publications.js';
|
|
53
|
+
/** The public surface resolves a workspace slug before anything touches the schema. */
|
|
54
|
+
export { resolveWorkspaceSegment } from './publications.js';
|
|
28
55
|
export * from './spaces.js';
|
|
29
56
|
export * from './versions.js';
|
|
30
57
|
//# sourceMappingURL=index.d.ts.map
|