@kernhq/module-quire 0.13.1 → 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.
Files changed (97) hide show
  1. package/dist/contract/models.d.ts +185 -0
  2. package/dist/contract/models.d.ts.map +1 -1
  3. package/dist/contract/models.js +133 -0
  4. package/dist/contract/models.js.map +1 -1
  5. package/dist/contract/permissions.d.ts +14 -0
  6. package/dist/contract/permissions.d.ts.map +1 -1
  7. package/dist/contract/permissions.js +82 -0
  8. package/dist/contract/permissions.js.map +1 -1
  9. package/dist/contract/properties.d.ts +4 -4
  10. package/dist/contract/router.d.ts +535 -8
  11. package/dist/contract/router.d.ts.map +1 -1
  12. package/dist/contract/router.js +133 -1
  13. package/dist/contract/router.js.map +1 -1
  14. package/dist/server/_impl.d.ts +519 -8
  15. package/dist/server/_impl.d.ts.map +1 -1
  16. package/dist/server/_impl.js +128 -0
  17. package/dist/server/_impl.js.map +1 -1
  18. package/dist/server/export/html.d.ts +67 -0
  19. package/dist/server/export/html.d.ts.map +1 -0
  20. package/dist/server/export/html.js +206 -0
  21. package/dist/server/export/html.js.map +1 -0
  22. package/dist/server/export/markdown.d.ts +51 -0
  23. package/dist/server/export/markdown.d.ts.map +1 -0
  24. package/dist/server/export/markdown.js +312 -0
  25. package/dist/server/export/markdown.js.map +1 -0
  26. package/dist/server/export/pdf.d.ts +20 -0
  27. package/dist/server/export/pdf.d.ts.map +1 -0
  28. package/dist/server/export/pdf.js +91 -0
  29. package/dist/server/export/pdf.js.map +1 -0
  30. package/dist/server/export/zip.d.ts +31 -0
  31. package/dist/server/export/zip.d.ts.map +1 -0
  32. package/dist/server/export/zip.js +158 -0
  33. package/dist/server/export/zip.js.map +1 -0
  34. package/dist/server/import/csv.d.ts +77 -0
  35. package/dist/server/import/csv.d.ts.map +1 -0
  36. package/dist/server/import/csv.js +263 -0
  37. package/dist/server/import/csv.js.map +1 -0
  38. package/dist/server/import/html.d.ts +52 -0
  39. package/dist/server/import/html.d.ts.map +1 -0
  40. package/dist/server/import/html.js +472 -0
  41. package/dist/server/import/html.js.map +1 -0
  42. package/dist/server/import/markdown.d.ts +63 -0
  43. package/dist/server/import/markdown.d.ts.map +1 -0
  44. package/dist/server/import/markdown.js +692 -0
  45. package/dist/server/import/markdown.js.map +1 -0
  46. package/dist/server/import/plan.d.ts +70 -0
  47. package/dist/server/import/plan.d.ts.map +1 -0
  48. package/dist/server/import/plan.js +761 -0
  49. package/dist/server/import/plan.js.map +1 -0
  50. package/dist/server/import/ydoc.d.ts +35 -0
  51. package/dist/server/import/ydoc.d.ts.map +1 -0
  52. package/dist/server/import/ydoc.js +91 -0
  53. package/dist/server/import/ydoc.js.map +1 -0
  54. package/dist/server/import/zip.d.ts +63 -0
  55. package/dist/server/import/zip.d.ts.map +1 -0
  56. package/dist/server/import/zip.js +308 -0
  57. package/dist/server/import/zip.js.map +1 -0
  58. package/dist/server/index.d.ts.map +1 -1
  59. package/dist/server/index.js +3 -1
  60. package/dist/server/index.js.map +1 -1
  61. package/dist/server/schema.d.ts +445 -1
  62. package/dist/server/schema.d.ts.map +1 -1
  63. package/dist/server/schema.js +146 -0
  64. package/dist/server/schema.js.map +1 -1
  65. package/dist/server/services/databases.d.ts +5 -5
  66. package/dist/server/services/export.d.ts +176 -0
  67. package/dist/server/services/export.d.ts.map +1 -0
  68. package/dist/server/services/export.js +822 -0
  69. package/dist/server/services/export.js.map +1 -0
  70. package/dist/server/services/import.d.ts +109 -0
  71. package/dist/server/services/import.d.ts.map +1 -0
  72. package/dist/server/services/import.js +570 -0
  73. package/dist/server/services/import.js.map +1 -0
  74. package/dist/server/services/index.d.ts +26 -1
  75. package/dist/server/services/index.d.ts.map +1 -1
  76. package/dist/server/services/index.js +60 -1
  77. package/dist/server/services/index.js.map +1 -1
  78. package/dist/server/services/versions.d.ts +1 -1
  79. package/migrations/0010_transfers.sql +154 -0
  80. package/migrations/meta/_journal.json +7 -0
  81. package/package.json +1 -1
  82. package/src/client/components/ExportDialog.svelte +685 -0
  83. package/src/client/components/ImportDialog.svelte +702 -0
  84. package/src/client/components/ImportReport.svelte +310 -0
  85. package/src/client/components/SidebarSpaces.svelte +79 -0
  86. package/src/client/i18n.ts +614 -0
  87. package/src/client/index.ts +32 -0
  88. package/src/client/mock.ts +318 -0
  89. package/src/client/module.ts +36 -0
  90. package/src/client/pages/PageView.svelte +36 -0
  91. package/src/client/pages/TransfersPage.svelte +570 -0
  92. package/src/client/permissions.ts +11 -0
  93. package/src/client/query.ts +23 -0
  94. package/src/client/transfers.ts +142 -0
  95. package/src/contract/models.ts +152 -0
  96. package/src/contract/permissions.ts +84 -0
  97. package/src/contract/router.ts +147 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACpC,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;IAC1C,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA;IAC5C,YAAY,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAClD,YAAY,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;CACnD;AAID,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAiB3D;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,uFAAuF;AACvF,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACpC,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;IAC1C,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA;IAC5C,YAAY,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAClD,YAAY,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAClD,OAAO,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACvC,OAAO,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;CACxC;AAID,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAsB3D;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAiCrE,CAAA;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,uFAAuF;AACvF,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
@@ -1,6 +1,9 @@
1
+ import { z } from 'zod';
1
2
  import { quireAccess } from './access.js';
2
3
  import { quireComments } from './comments.js';
3
4
  import { quireDatabases } from './databases.js';
5
+ import { quireExport } from './export.js';
6
+ import { quireImport } from './import.js';
4
7
  import { quireOrganisation } from './organisation.js';
5
8
  import { quirePages } from './pages.js';
6
9
  import { quirePublications } from './publications.js';
@@ -14,22 +17,78 @@ export function quireServices(kernel) {
14
17
  return existing;
15
18
  const access = quireAccess(kernel);
16
19
  const versions = quireVersions(kernel, access);
20
+ // An import writes pages *and* databases, so it is the one service built on another one: it needs
21
+ // `databases` to mint the properties whose keys its rows are keyed by. See `import.ts`'s `write`.
22
+ const databases = quireDatabases(kernel, access);
17
23
  const services = {
18
24
  access,
19
25
  spaces: quireSpaces(access),
20
26
  pages: quirePages(access),
21
27
  versions,
22
28
  comments: quireComments(access),
23
- databases: quireDatabases(kernel, access),
29
+ databases,
24
30
  organisation: quireOrganisation(access),
25
31
  publications: quirePublications(kernel, access, versions),
32
+ exports: quireExport(kernel, access),
33
+ imports: quireImport(kernel, access, databases),
26
34
  };
27
35
  cache.set(kernel, services);
28
36
  return services;
29
37
  }
38
+ /**
39
+ * The background work this module hands to a worker process, gathered here rather than in
40
+ * `index.ts`.
41
+ *
42
+ * One line in the module definition, so adding a job is an edit to this file and not to the file
43
+ * every other part of the module is also editing. The names reach pg-boss as `quire.<name>` — the
44
+ * kernel prefixes them — and only a process started with a worker role runs them; an API process
45
+ * registers nothing and merely sends.
46
+ *
47
+ * `retryLimit` is deliberately small. An export that fails for a reason of its own — Gotenberg is
48
+ * not running, a page will not render — records that reason on its row and returns *successfully*,
49
+ * because it has finished: it failed, and it said so, and repeating it would only write the same
50
+ * sentence three more times. A throw out of the handler means something underneath went wrong
51
+ * (the database, the broker), which is the case a retry is for.
52
+ */
53
+ export const quireJobs = [
54
+ {
55
+ name: 'export',
56
+ schema: z.object({ workspaceId: z.uuid(), jobId: z.uuid() }),
57
+ handler: async (input, { kernel }) => {
58
+ await quireServices(kernel).exports.run(input.workspaceId, input.jobId);
59
+ },
60
+ options: { retryLimit: 2, retryDelay: 30, expireInSeconds: 900 },
61
+ },
62
+ /**
63
+ * An import, with **no retries at all** — the one place in this module where that is the safe
64
+ * setting rather than the timid one.
65
+ *
66
+ * `run` is idempotent against its own row (`done` and `failed` are terminal, and it claims the row
67
+ * before it does anything), so a retry after a *completed* run is a no-op. What a retry cannot be
68
+ * safe against is the case pg-boss actually retries: a handler that threw. An import commits its
69
+ * pages and its finished row in one transaction, so a throw means nothing was written — but a
70
+ * *lost connection after the commit* looks exactly the same from here, and running it again would
71
+ * write every page in the archive a second time. Two hundred duplicate pages in somebody's space
72
+ * is a worse outcome than a job that says it failed and can be started again by a person who can
73
+ * see what is in the space.
74
+ *
75
+ * The window is longer than an export's for the obvious reason: reading a few hundred megabytes of
76
+ * zip and writing four thousand rows is not a fifteen-minute job on a busy instance.
77
+ */
78
+ {
79
+ name: 'import',
80
+ schema: z.object({ workspaceId: z.uuid(), jobId: z.uuid() }),
81
+ handler: async (input, { kernel }) => {
82
+ await quireServices(kernel).imports.run(input.workspaceId, input.jobId);
83
+ },
84
+ options: { retryLimit: 0, expireInSeconds: 3600 },
85
+ },
86
+ ];
30
87
  export * from './access.js';
31
88
  export * from './comments.js';
32
89
  export * from './databases.js';
90
+ export * from './export.js';
91
+ export * from './import.js';
33
92
  export * from './organisation.js';
34
93
  export * from './pages.js';
35
94
  export * from './publications.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAa7C,MAAM,KAAK,GAAG,IAAI,OAAO,EAAyB,CAAA;AAElD,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAClC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9C,MAAM,QAAQ,GAAkB;QAC9B,MAAM;QACN,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;QAC3B,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC;QACzB,QAAQ;QACR,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC;QAC/B,SAAS,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC;QACzC,YAAY,EAAE,iBAAiB,CAAC,MAAM,CAAC;QACvC,YAAY,EAAE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;KAC1D,CAAA;IACD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC3B,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,uFAAuF;AACvF,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"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"}
@@ -76,9 +76,9 @@ export declare function quireVersions(kernel: Kernel, access: QuireAccess): {
76
76
  kind: string;
77
77
  size: number;
78
78
  authorId: string | null;
79
- text: string;
80
79
  html: string | null;
81
80
  state: Buffer<ArrayBufferLike>;
81
+ text: string;
82
82
  snapshot: Buffer<ArrayBufferLike> | null;
83
83
  }>;
84
84
  /** What a reader is served, once somebody decides it is ready. */
@@ -0,0 +1,154 @@
1
+ -- Getting work in and out: a request to export a page, a subtree or a space, and a request to import
2
+ -- a Notion, Confluence or Markdown export into a space.
3
+ --
4
+ -- Hand-written rather than generated, like 0001, 0006 and 0008, because drizzle-kit writes neither
5
+ -- the policy nor the guards. Every statement is idempotent: `create table`, `create index` and
6
+ -- `create policy` throw on a replay, and a module migration that throws takes down the **whole host
7
+ -- service** rather than its own module — `core` hosts five. Drizzle keys applied migrations by
8
+ -- content hash, so regenerating the journal replays every file against a schema that already has its
9
+ -- objects.
10
+ --
11
+ --
12
+ -- WHY THESE ARE ROWS AND NOT REQUESTS
13
+ --
14
+ -- The work is unbounded on both sides. A space is a tree of arbitrary size; PDF means a round trip to
15
+ -- Gotenberg per page; a Notion export is a zip with thousands of files in it. A job that outlives its
16
+ -- HTTP request is what lets progress be reported, a failure be read afterwards by somebody who was
17
+ -- not watching, and a retry happen without the browser having stayed open. That is the whole reason
18
+ -- `state`, `counts`, `error` and `finished_at` are columns rather than fields of a response.
19
+ --
20
+ --
21
+ -- WHAT THESE TABLES DO NOT DO
22
+ --
23
+ -- Neither of them is a permission. "You may not export a page you may not read" and "an import writes
24
+ -- only into a space you may write to" are decided by the procedure, against the same space bindings
25
+ -- every other read in this module goes through, before a row is written at all. The policy below is
26
+ -- the tenant fence and nothing more — it stops workspace B seeing workspace A's jobs, and it has
27
+ -- nothing to say about which pages within one workspace went into an artefact. Do not read a row's
28
+ -- existence as evidence that anybody was allowed to ask for it.
29
+ CREATE TABLE IF NOT EXISTS "mod_quire"."export_jobs" (
30
+ "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
31
+ "workspace_id" uuid NOT NULL,
32
+ -- Not null, where every other `created_by` in this schema is nullable. The artefact is the hazard:
33
+ -- a subtree export flattens pages with different readerships into one file, so whoever may fetch
34
+ -- it may read everything that went into it. Fencing the download to the person who asked needs a
35
+ -- person to fence it to, and a row whose requester has gone null cannot be fenced.
36
+ "requested_by" uuid NOT NULL,
37
+ -- `page` | `subtree` | `space`
38
+ "scope" text NOT NULL,
39
+ -- the page for `page` and `subtree`, the space for `space`; `scope` says which
40
+ "target_id" uuid NOT NULL,
41
+ -- `markdown` | `html` | `docx` | `pdf`
42
+ "format" text NOT NULL,
43
+ -- `queued` | `running` | `done` | `failed`
44
+ "state" text DEFAULT 'queued' NOT NULL,
45
+ -- The artefact in storage: written when the job finishes, null until then. Null while running is
46
+ -- what keeps a half file from being offered — the uploader writes the bytes, then the job records
47
+ -- the id, so this never points at a file that is still arriving. An id is not a URL: the download
48
+ -- is a signed URL a procedure mints, never a storage key a client assembles. See 0009 for what
49
+ -- that rule cost the last time it was broken.
50
+ "file_id" uuid,
51
+ -- Diagnostic text, in the language of whatever failed. Not a user-facing string: what a screen
52
+ -- shows comes from a message key chosen from `state`, and this is what makes the same failure
53
+ -- answerable a week later.
54
+ "error" text,
55
+ -- `{ total, done, skipped, failed }`. `skipped` is load-bearing: a subtree export by somebody who
56
+ -- may not read one of its children leaves that child out, and the count is the difference between
57
+ -- an export that is missing pages and an export that says so.
58
+ "counts" jsonb DEFAULT '{}'::jsonb NOT NULL,
59
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
60
+ -- null while `queued` or `running`; set once, on the move to `done` or `failed`
61
+ "finished_at" timestamp with time zone
62
+ );
63
+ --> statement-breakpoint
64
+
65
+ -- The same shape pointed the other way, with one addition that carries the weight of the feature.
66
+ --
67
+ -- `report` is jsonb rather than a table, and that is a decision rather than a shortcut.
68
+ --
69
+ -- It is written by one job and read whole, as one document, by one screen: the person who ran the
70
+ -- import looking at what happened to their import. Nothing joins to an entry, nothing updates an
71
+ -- entry after the fact, nothing holds a foreign key into one, and no entry outlives its job. A
72
+ -- row-per-file table would be four thousand inserts to render a list only ever fetched by
73
+ -- `where job_id = $1`, plus a second tenant table with its own `workspace_id`, its own policy, its
74
+ -- own `TENANT_TABLES` entry and its own retention rule — all to store a document. Here, dropping the
75
+ -- job drops its report; there is nothing to sweep. The size is the part worth checking rather than
76
+ -- assuming: an entry is a path, one of three words, and either a uuid or a sentence, so a
77
+ -- five-thousand-file import is a few hundred kilobytes — one TOASTed value, read once.
78
+ --
79
+ -- **What would change if somebody wanted to query across imports** — "every file that failed for this
80
+ -- reason, across every import this month" — is that this stops being the right shape, and not by a
81
+ -- little. `jsonb_array_elements` over the whole table can answer it, but no index helps: a GIN index
82
+ -- on the column serves containment (`@>`), not a predicate plus an ordering inside the array. The
83
+ -- change is a `mod_quire.import_entries` table — `job_id`, `workspace_id`, `path`, `outcome`,
84
+ -- `page_id`, `reason` — indexed on `(workspace_id, outcome, created_at)`, written by the same worker,
85
+ -- with the existing rows backfilled by expanding this column through `jsonb_to_recordset`. It is a
86
+ -- tenant table, so it arrives with the full triple and a `TENANT_TABLES` entry, and it needs the
87
+ -- retention rule the column gets for free. The threshold is exactly that question: while every read
88
+ -- names one job, the column is right.
89
+ CREATE TABLE IF NOT EXISTS "mod_quire"."import_jobs" (
90
+ "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
91
+ "workspace_id" uuid NOT NULL,
92
+ -- not null for the same reason as `export_jobs.requested_by`
93
+ "requested_by" uuid NOT NULL,
94
+ -- `notion` | `confluence` | `markdown`
95
+ "source" text NOT NULL,
96
+ -- the space being written into. An import always targets one space; there is no `scope` here.
97
+ "target_id" uuid NOT NULL,
98
+ -- The uploaded archive, and **not** called `file_id` on purpose. `export_jobs.file_id` is the file
99
+ -- a job produces and is null until it succeeds; this is the file a job consumes and exists before
100
+ -- the job does. One name pointing in two directions across two tables read by one screen is how a
101
+ -- worker writes its output id over the pointer to its input — losing the archive, so the job
102
+ -- cannot be retried and nothing reports that anything went missing.
103
+ "source_file_id" uuid NOT NULL,
104
+ -- `queued` | `running` | `done` | `failed`
105
+ "state" text DEFAULT 'queued' NOT NULL,
106
+ -- why the *job* failed; why one *file* failed is that file's entry in `report`
107
+ "error" text,
108
+ -- `{ total, done, skipped, failed }` — the same counters as an export, over files rather than pages
109
+ "counts" jsonb DEFAULT '{}'::jsonb NOT NULL,
110
+ -- one entry per file: `{ path, outcome, pageId, reason }`, where outcome is imported|skipped|failed
111
+ "report" jsonb DEFAULT '[]'::jsonb NOT NULL,
112
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
113
+ -- null while `queued` or `running`; set once, on the move to `done` or `failed`
114
+ "finished_at" timestamp with time zone
115
+ );
116
+ --> statement-breakpoint
117
+
118
+ -- Newest first, because that is the only order a list of jobs is ever read in — the one you just
119
+ -- started is the one you are looking for. `DESC NULLS LAST` rather than a plain index scanned
120
+ -- backwards so the ordering is the index's own, matching `recent_views_ws_user_idx` in 0007.
121
+ CREATE INDEX IF NOT EXISTS "export_jobs_ws_created_idx" ON "mod_quire"."export_jobs" USING btree ("workspace_id","created_at" DESC NULLS LAST);--> statement-breakpoint
122
+
123
+ -- "What is still running here", asked by the worker picking up work and by every client polling a
124
+ -- job it started. Leading with the state after the workspace keeps that probe off the rows of every
125
+ -- export the workspace has ever finished.
126
+ CREATE INDEX IF NOT EXISTS "export_jobs_ws_state_idx" ON "mod_quire"."export_jobs" USING btree ("workspace_id","state","created_at");--> statement-breakpoint
127
+ CREATE INDEX IF NOT EXISTS "import_jobs_ws_created_idx" ON "mod_quire"."import_jobs" USING btree ("workspace_id","created_at" DESC NULLS LAST);--> statement-breakpoint
128
+ CREATE INDEX IF NOT EXISTS "import_jobs_ws_state_idx" ON "mod_quire"."import_jobs" USING btree ("workspace_id","state","created_at");--> statement-breakpoint
129
+
130
+ -- Row-level security, the same triple every tenant table gets.
131
+ --
132
+ -- `force` matters: without it the table owner bypasses the policy, and the owner is the role the
133
+ -- service connects as. A *superuser* bypasses RLS whatever this says, and the development and CI
134
+ -- roles are superusers — so a test that does not connect as an unprivileged NOBYPASSRLS role proves
135
+ -- nothing about isolation.
136
+ --
137
+ -- Note what this does and does not fence, because these two tables invite the wrong reading. It is
138
+ -- the tenant boundary: workspace B cannot see workspace A's jobs, in either direction. It says
139
+ -- nothing about which *pages* went into an artefact, nothing about whether the requester was allowed
140
+ -- to ask, and nothing about who may download the result. Those are the procedure's job, decided
141
+ -- against the same space bindings as every other read in this module, before the row exists.
142
+ ALTER TABLE "mod_quire"."export_jobs" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
143
+ ALTER TABLE "mod_quire"."export_jobs" FORCE ROW LEVEL SECURITY;--> statement-breakpoint
144
+ DROP POLICY IF EXISTS "export_jobs_ws_isolation" ON "mod_quire"."export_jobs";--> statement-breakpoint
145
+ CREATE POLICY "export_jobs_ws_isolation" ON "mod_quire"."export_jobs"
146
+ USING (workspace_id::text = current_setting('app.workspace_id', true))
147
+ WITH CHECK (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
148
+
149
+ ALTER TABLE "mod_quire"."import_jobs" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
150
+ ALTER TABLE "mod_quire"."import_jobs" FORCE ROW LEVEL SECURITY;--> statement-breakpoint
151
+ DROP POLICY IF EXISTS "import_jobs_ws_isolation" ON "mod_quire"."import_jobs";--> statement-breakpoint
152
+ CREATE POLICY "import_jobs_ws_isolation" ON "mod_quire"."import_jobs"
153
+ USING (workspace_id::text = current_setting('app.workspace_id', true))
154
+ WITH CHECK (workspace_id::text = current_setting('app.workspace_id', true));
@@ -71,6 +71,13 @@
71
71
  "when": 1787954227617,
72
72
  "tag": "0009_public_asset_references",
73
73
  "breakpoints": true
74
+ },
75
+ {
76
+ "idx": 10,
77
+ "version": "7",
78
+ "when": 1788040627617,
79
+ "tag": "0010_transfers",
80
+ "breakpoints": true
74
81
  }
75
82
  ]
76
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernhq/module-quire",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Kern Quire: collaborative documents, spaces and page trees",
5
5
  "homepage": "https://github.com/KernAIO/module-quire#readme",
6
6
  "license": "AGPL-3.0-only",