@kernhq/module-quire 0.2.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 (72) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +71 -0
  3. package/dist/client/rank.d.ts +42 -0
  4. package/dist/client/rank.d.ts.map +1 -0
  5. package/dist/client/rank.js +97 -0
  6. package/dist/client/rank.js.map +1 -0
  7. package/dist/contract/capabilities.d.ts +35 -0
  8. package/dist/contract/capabilities.d.ts.map +1 -0
  9. package/dist/contract/capabilities.js +35 -0
  10. package/dist/contract/capabilities.js.map +1 -0
  11. package/dist/contract/events.d.ts +58 -0
  12. package/dist/contract/events.d.ts.map +1 -0
  13. package/dist/contract/events.js +18 -0
  14. package/dist/contract/events.js.map +1 -0
  15. package/dist/contract/index.d.ts +6 -0
  16. package/dist/contract/index.d.ts.map +1 -0
  17. package/dist/contract/index.js +6 -0
  18. package/dist/contract/index.js.map +1 -0
  19. package/dist/contract/models.d.ts +95 -0
  20. package/dist/contract/models.d.ts.map +1 -0
  21. package/dist/contract/models.js +82 -0
  22. package/dist/contract/models.js.map +1 -0
  23. package/dist/contract/permissions.d.ts +51 -0
  24. package/dist/contract/permissions.d.ts.map +1 -0
  25. package/dist/contract/permissions.js +59 -0
  26. package/dist/contract/permissions.js.map +1 -0
  27. package/dist/contract/router.d.ts +713 -0
  28. package/dist/contract/router.d.ts.map +1 -0
  29. package/dist/contract/router.js +109 -0
  30. package/dist/contract/router.js.map +1 -0
  31. package/dist/server/_impl.d.ts +815 -0
  32. package/dist/server/_impl.d.ts.map +1 -0
  33. package/dist/server/_impl.js +189 -0
  34. package/dist/server/_impl.js.map +1 -0
  35. package/dist/server/index.d.ts +3 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +87 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/schema.d.ts +540 -0
  40. package/dist/server/schema.d.ts.map +1 -0
  41. package/dist/server/schema.js +86 -0
  42. package/dist/server/schema.js.map +1 -0
  43. package/dist/server/services/access.d.ts +78 -0
  44. package/dist/server/services/access.d.ts.map +1 -0
  45. package/dist/server/services/access.js +96 -0
  46. package/dist/server/services/access.js.map +1 -0
  47. package/dist/server/services/index.d.ts +15 -0
  48. package/dist/server/services/index.d.ts.map +1 -0
  49. package/dist/server/services/index.js +22 -0
  50. package/dist/server/services/index.js.map +1 -0
  51. package/dist/server/services/pages.d.ts +190 -0
  52. package/dist/server/services/pages.d.ts.map +1 -0
  53. package/dist/server/services/pages.js +242 -0
  54. package/dist/server/services/pages.js.map +1 -0
  55. package/dist/server/services/spaces.d.ts +95 -0
  56. package/dist/server/services/spaces.d.ts.map +1 -0
  57. package/dist/server/services/spaces.js +100 -0
  58. package/dist/server/services/spaces.js.map +1 -0
  59. package/migrations/0000_init.sql +43 -0
  60. package/migrations/0001_rls.sql +17 -0
  61. package/migrations/meta/0000_snapshot.json +358 -0
  62. package/migrations/meta/_journal.json +20 -0
  63. package/package.json +78 -0
  64. package/src/client/index.ts +67 -0
  65. package/src/client/rank.test.ts +92 -0
  66. package/src/client/rank.ts +100 -0
  67. package/src/contract/capabilities.ts +36 -0
  68. package/src/contract/events.ts +22 -0
  69. package/src/contract/index.ts +5 -0
  70. package/src/contract/models.ts +93 -0
  71. package/src/contract/permissions.ts +59 -0
  72. package/src/contract/router.ts +121 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Who may do what to a space or a page.
3
+ *
4
+ * The permission engine resolves bindings nearest-scope-first, so this file's only job is to hand it
5
+ * the right scope chain: a page is scoped to itself, with its space and then the workspace as
6
+ * parents. That is what makes "everyone may read the Handbook, the design team may write it, and
7
+ * this one contractor may read exactly one page of it" expressible without a second permission
8
+ * system — and what makes a restriction on a parent page apply to its children, because the chain
9
+ * carries every ancestor.
10
+ */
11
+ import type { Principal } from '@kernhq/contracts';
12
+ import { type Kernel, type Tx } from '@kernhq/kernel';
13
+ export interface PageScope {
14
+ pageId: string;
15
+ spaceId: string;
16
+ /** every ancestor, nearest first — a restriction on a parent has to reach its children */
17
+ ancestorIds: string[];
18
+ }
19
+ export declare function quireAccess(kernel: Kernel): {
20
+ /** May this user do `permission` anywhere in the space? */
21
+ canSpace(principal: Principal, permission: string, workspaceId: string, spaceId: string): Promise<boolean>;
22
+ requireSpace(principal: Principal, permission: string, workspaceId: string, spaceId: string): Promise<void>;
23
+ /**
24
+ * The same question about one page. The page is the narrowest scope, and every ancestor page is
25
+ * in the chain above it so a restriction set higher up is inherited rather than having to be
26
+ * copied down.
27
+ */
28
+ canPage(principal: Principal, permission: string, workspaceId: string, scope: PageScope): Promise<boolean>;
29
+ requirePage(principal: Principal, permission: string, workspaceId: string, scope: PageScope): Promise<void>;
30
+ /**
31
+ * The scope of a page, walked up through its parents.
32
+ *
33
+ * Recursive rather than one query per level: a deep tree would otherwise cost a round trip per
34
+ * ancestor on every read. The `cycle` clause is not paranoia — a move that made a page its own
35
+ * ancestor would otherwise hang the connection rather than fail.
36
+ */
37
+ scopeOf(tx: Tx, workspaceId: string, pageId: string): Promise<PageScope>;
38
+ /** The space row, or `notFound` — used before every space-scoped check so it cannot be skipped. */
39
+ spaceRow(tx: Tx, workspaceId: string, spaceId: string): Promise<{
40
+ id: string;
41
+ workspaceId: string;
42
+ key: string;
43
+ name: string;
44
+ description: string;
45
+ icon: string | null;
46
+ visibility: string;
47
+ homepageId: string | null;
48
+ createdBy: string | null;
49
+ createdAt: Date;
50
+ updatedAt: Date;
51
+ archivedAt: Date | null;
52
+ }>;
53
+ /** The page row, excluding anything in the trash unless asked for. */
54
+ pageRow(tx: Tx, workspaceId: string, pageId: string, opts?: {
55
+ includeTrashed?: boolean;
56
+ }): Promise<{
57
+ id: string;
58
+ workspaceId: string;
59
+ spaceId: string;
60
+ parentId: string | null;
61
+ position: string;
62
+ kind: string;
63
+ title: string;
64
+ icon: string | null;
65
+ coverUrl: string | null;
66
+ publishedVersionId: string | null;
67
+ hasUnpublishedChanges: boolean;
68
+ text: string;
69
+ createdBy: string | null;
70
+ updatedBy: string | null;
71
+ createdAt: Date;
72
+ updatedAt: Date;
73
+ archivedAt: Date | null;
74
+ deletedAt: Date | null;
75
+ }>;
76
+ };
77
+ export type QuireAccess = ReturnType<typeof quireAccess>;
78
+ //# sourceMappingURL=access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../../src/server/services/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,gBAAgB,CAAA;AAIhE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,0FAA0F;IAC1F,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM;IAStC,2DAA2D;wBACvC,SAAS,cAAc,MAAM,eAAe,MAAM,WAAW,MAAM;4BAI/D,SAAS,cAAc,MAAM,eAAe,MAAM,WAAW,MAAM;IAI3F;;;;OAIG;uBACgB,SAAS,cAAc,MAAM,eAAe,MAAM,SAAS,SAAS;2BAc1E,SAAS,cACR,MAAM,eACL,MAAM,SACZ,SAAS,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB;;;;;;OAMG;gBACe,EAAE,eAAe,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAuB9E,mGAAmG;iBAChF,EAAE,eAAe,MAAM,WAAW,MAAM;;;;;;;;;;;;;;IAU3D,sEAAsE;gBACpD,EAAE,eAAe,MAAM,UAAU,MAAM,SAAQ;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;EAchG;AACD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA"}
@@ -0,0 +1,96 @@
1
+ import { KernError } from '@kernhq/kernel';
2
+ import { and, eq, isNull, sql } from 'drizzle-orm';
3
+ import { pages, spaces } from '../schema.js';
4
+ export function quireAccess(kernel) {
5
+ const spaceScope = (workspaceId, spaceId) => ({
6
+ kind: 'space',
7
+ id: spaceId,
8
+ parents: [{ kind: 'workspace', id: workspaceId }],
9
+ workspaceId,
10
+ });
11
+ return {
12
+ /** May this user do `permission` anywhere in the space? */
13
+ canSpace(principal, permission, workspaceId, spaceId) {
14
+ return kernel.authz.can(principal, permission, spaceScope(workspaceId, spaceId));
15
+ },
16
+ requireSpace(principal, permission, workspaceId, spaceId) {
17
+ return kernel.authz.require(principal, permission, spaceScope(workspaceId, spaceId));
18
+ },
19
+ /**
20
+ * The same question about one page. The page is the narrowest scope, and every ancestor page is
21
+ * in the chain above it so a restriction set higher up is inherited rather than having to be
22
+ * copied down.
23
+ */
24
+ canPage(principal, permission, workspaceId, scope) {
25
+ return kernel.authz.can(principal, permission, {
26
+ kind: 'object',
27
+ id: scope.pageId,
28
+ parents: [
29
+ ...scope.ancestorIds.map((id) => ({ kind: 'object', id })),
30
+ { kind: 'space', id: scope.spaceId },
31
+ { kind: 'workspace', id: workspaceId },
32
+ ],
33
+ workspaceId,
34
+ });
35
+ },
36
+ async requirePage(principal, permission, workspaceId, scope) {
37
+ if (!(await this.canPage(principal, permission, workspaceId, scope)))
38
+ throw KernError.forbidden(permission);
39
+ },
40
+ /**
41
+ * The scope of a page, walked up through its parents.
42
+ *
43
+ * Recursive rather than one query per level: a deep tree would otherwise cost a round trip per
44
+ * ancestor on every read. The `cycle` clause is not paranoia — a move that made a page its own
45
+ * ancestor would otherwise hang the connection rather than fail.
46
+ */
47
+ async scopeOf(tx, workspaceId, pageId) {
48
+ const rows = await tx.execute(sql `
49
+ with recursive chain as (
50
+ select id, space_id, parent_id, 0 as depth
51
+ from mod_quire.pages
52
+ where workspace_id = ${workspaceId}::uuid and id = ${pageId}::uuid
53
+ union all
54
+ select p.id, p.space_id, p.parent_id, chain.depth + 1
55
+ from mod_quire.pages p
56
+ join chain on p.id = chain.parent_id
57
+ where p.workspace_id = ${workspaceId}::uuid
58
+ ) cycle id set looped using path
59
+ select id, space_id, depth from chain order by depth
60
+ `);
61
+ const self = rows.rows[0];
62
+ if (!self)
63
+ throw KernError.notFound('Page');
64
+ return {
65
+ pageId: self.id,
66
+ spaceId: self.space_id,
67
+ ancestorIds: rows.rows.slice(1).map((r) => r.id),
68
+ };
69
+ },
70
+ /** The space row, or `notFound` — used before every space-scoped check so it cannot be skipped. */
71
+ async spaceRow(tx, workspaceId, spaceId) {
72
+ const [row] = await tx
73
+ .select()
74
+ .from(spaces)
75
+ .where(and(eq(spaces.workspaceId, workspaceId), eq(spaces.id, spaceId)))
76
+ .limit(1);
77
+ if (!row)
78
+ throw KernError.notFound('Space');
79
+ return row;
80
+ },
81
+ /** The page row, excluding anything in the trash unless asked for. */
82
+ async pageRow(tx, workspaceId, pageId, opts = {}) {
83
+ const [row] = await tx
84
+ .select()
85
+ .from(pages)
86
+ .where(opts.includeTrashed
87
+ ? and(eq(pages.workspaceId, workspaceId), eq(pages.id, pageId))
88
+ : and(eq(pages.workspaceId, workspaceId), eq(pages.id, pageId), isNull(pages.deletedAt)))
89
+ .limit(1);
90
+ if (!row)
91
+ throw KernError.notFound('Page');
92
+ return row;
93
+ },
94
+ };
95
+ }
96
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../../src/server/services/access.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Kernel } from '@kernhq/kernel';
2
+ import { quireAccess } from './access.js';
3
+ import { quirePages } from './pages.js';
4
+ import { quireSpaces } from './spaces.js';
5
+ export interface QuireServices {
6
+ access: ReturnType<typeof quireAccess>;
7
+ spaces: ReturnType<typeof quireSpaces>;
8
+ pages: ReturnType<typeof quirePages>;
9
+ }
10
+ /** One set of services per kernel: they are stateless, and rebuilding them per request is waste. */
11
+ export declare function quireServices(kernel: Kernel): QuireServices;
12
+ export * from './access.js';
13
+ export * from './pages.js';
14
+ export * from './spaces.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,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;CACrC;AAID,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAW3D;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { quireAccess } from './access.js';
2
+ import { quirePages } from './pages.js';
3
+ import { quireSpaces } from './spaces.js';
4
+ const cache = new WeakMap();
5
+ /** One set of services per kernel: they are stateless, and rebuilding them per request is waste. */
6
+ export function quireServices(kernel) {
7
+ const existing = cache.get(kernel);
8
+ if (existing)
9
+ return existing;
10
+ const access = quireAccess(kernel);
11
+ const services = {
12
+ access,
13
+ spaces: quireSpaces(access),
14
+ pages: quirePages(access),
15
+ };
16
+ cache.set(kernel, services);
17
+ return services;
18
+ }
19
+ export * from './access.js';
20
+ export * from './pages.js';
21
+ export * from './spaces.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAQzC,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,GAAkB;QAC9B,MAAM;QACN,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;QAC3B,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC;KAC1B,CAAA;IACD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC3B,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,190 @@
1
+ import { type Principal } from '@kernhq/contracts';
2
+ import { type Tx } from '@kernhq/kernel';
3
+ import type { Page, PageNode } from '../../contract/index.js';
4
+ import { pages } from '../schema.js';
5
+ import type { QuireAccess } from './access.js';
6
+ type PageRow = typeof pages.$inferSelect;
7
+ /** The name this page's prose is synchronised under, on the collab service. */
8
+ export declare function documentNameOf(row: {
9
+ workspaceId: string;
10
+ id: string;
11
+ }): string;
12
+ export declare function toPage(row: PageRow): Page;
13
+ export declare function quirePages(access: QuireAccess): {
14
+ /**
15
+ * Every page in the space, flat, ordered so the caller can build the tree without sorting.
16
+ *
17
+ * One query for the whole space rather than one per expanded level: a wiki sidebar shows every
18
+ * level at once, and paging by level turns opening a space into a request per node.
19
+ */
20
+ tree(tx: Tx, workspaceId: string, spaceId: string, includeArchived: boolean): Promise<PageNode[]>;
21
+ get(tx: Tx, workspaceId: string, pageId: string): Promise<{
22
+ id: string;
23
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
24
+ spaceId: string;
25
+ parentId: string | null;
26
+ position: string;
27
+ kind: "page" | "live" | "database";
28
+ title: string;
29
+ icon: string | null;
30
+ coverUrl: string | null;
31
+ publishedVersionId: string | null;
32
+ hasUnpublishedChanges: boolean;
33
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
34
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ archivedAt: string | null;
38
+ deletedAt: string | null;
39
+ }>;
40
+ trash(tx: Tx, workspaceId: string, spaceId: string, limit: number, cursor: string | null): Promise<{
41
+ items: {
42
+ id: string;
43
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
44
+ spaceId: string;
45
+ parentId: string | null;
46
+ position: string;
47
+ kind: "page" | "live" | "database";
48
+ title: string;
49
+ icon: string | null;
50
+ coverUrl: string | null;
51
+ publishedVersionId: string | null;
52
+ hasUnpublishedChanges: boolean;
53
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
54
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
55
+ createdAt: string;
56
+ updatedAt: string;
57
+ archivedAt: string | null;
58
+ deletedAt: string | null;
59
+ }[];
60
+ nextCursor: string | null;
61
+ }>;
62
+ create(tx: Tx, principal: Principal, workspaceId: string, input: {
63
+ spaceId: string;
64
+ parentId: string | null;
65
+ title: string;
66
+ kind: string;
67
+ icon: string | null;
68
+ afterId: string | null;
69
+ }): Promise<{
70
+ id: string;
71
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
72
+ spaceId: string;
73
+ parentId: string | null;
74
+ position: string;
75
+ kind: "page" | "live" | "database";
76
+ title: string;
77
+ icon: string | null;
78
+ coverUrl: string | null;
79
+ publishedVersionId: string | null;
80
+ hasUnpublishedChanges: boolean;
81
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
82
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
83
+ createdAt: string;
84
+ updatedAt: string;
85
+ archivedAt: string | null;
86
+ deletedAt: string | null;
87
+ }>;
88
+ update(tx: Tx, principal: Principal, workspaceId: string, pageId: string, patch: Partial<Pick<PageRow, "title" | "icon" | "coverUrl" | "kind">>): Promise<{
89
+ id: string;
90
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
91
+ spaceId: string;
92
+ parentId: string | null;
93
+ position: string;
94
+ kind: "page" | "live" | "database";
95
+ title: string;
96
+ icon: string | null;
97
+ coverUrl: string | null;
98
+ publishedVersionId: string | null;
99
+ hasUnpublishedChanges: boolean;
100
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
101
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
102
+ createdAt: string;
103
+ updatedAt: string;
104
+ archivedAt: string | null;
105
+ deletedAt: string | null;
106
+ }>;
107
+ /**
108
+ * Reparent and reorder in one step.
109
+ *
110
+ * The cycle guard is the part worth reading: dragging a page onto one of its own descendants
111
+ * would otherwise produce a subtree with no root, invisible in every query that walks down from
112
+ * the top and impossible to reach or delete.
113
+ */
114
+ move(tx: Tx, principal: Principal, workspaceId: string, pageId: string, parentId: string | null, afterId: string | null): Promise<{
115
+ id: string;
116
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
117
+ spaceId: string;
118
+ parentId: string | null;
119
+ position: string;
120
+ kind: "page" | "live" | "database";
121
+ title: string;
122
+ icon: string | null;
123
+ coverUrl: string | null;
124
+ publishedVersionId: string | null;
125
+ hasUnpublishedChanges: boolean;
126
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
127
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
128
+ createdAt: string;
129
+ updatedAt: string;
130
+ archivedAt: string | null;
131
+ deletedAt: string | null;
132
+ }>;
133
+ archive(tx: Tx, principal: Principal, workspaceId: string, pageId: string, archived: boolean): Promise<{
134
+ id: string;
135
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
136
+ spaceId: string;
137
+ parentId: string | null;
138
+ position: string;
139
+ kind: "page" | "live" | "database";
140
+ title: string;
141
+ icon: string | null;
142
+ coverUrl: string | null;
143
+ publishedVersionId: string | null;
144
+ hasUnpublishedChanges: boolean;
145
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
146
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
147
+ createdAt: string;
148
+ updatedAt: string;
149
+ archivedAt: string | null;
150
+ deletedAt: string | null;
151
+ }>;
152
+ /** Every page in the subtree rooted at `pageId`, including it, nearest first. */
153
+ subtreeIds(tx: Tx, workspaceId: string, pageId: string): Promise<string[]>;
154
+ /** Into the trash, with every descendant — a page whose parent is gone is unreachable. */
155
+ trashPage(tx: Tx, workspaceId: string, pageId: string): Promise<{
156
+ ids: string[];
157
+ }>;
158
+ /**
159
+ * Back out of the trash. Only the page asked for and its descendants come back, and only if its
160
+ * parent is still there — restoring under a deleted parent would put it somewhere unreachable.
161
+ */
162
+ restore(tx: Tx, workspaceId: string, pageId: string): Promise<{
163
+ id: string;
164
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
165
+ spaceId: string;
166
+ parentId: string | null;
167
+ position: string;
168
+ kind: "page" | "live" | "database";
169
+ title: string;
170
+ icon: string | null;
171
+ coverUrl: string | null;
172
+ publishedVersionId: string | null;
173
+ hasUnpublishedChanges: boolean;
174
+ createdBy: (string & import("zod").$brand<"UserId">) | null;
175
+ updatedBy: (string & import("zod").$brand<"UserId">) | null;
176
+ createdAt: string;
177
+ updatedAt: string;
178
+ archivedAt: string | null;
179
+ deletedAt: string | null;
180
+ }>;
181
+ /** Gone. The caller is responsible for telling collab to forget the documents. */
182
+ purge(tx: Tx, workspaceId: string, pageId: string): Promise<{
183
+ ids: string[];
184
+ }>;
185
+ /** The first page of a brand new space, so a space is never an empty screen. */
186
+ firstPosition: () => string;
187
+ };
188
+ export type QuirePages = ReturnType<typeof quirePages>;
189
+ export {};
190
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../../src/server/services/pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAa,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAG3D,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAE7D,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,KAAK,OAAO,GAAG,OAAO,KAAK,CAAC,YAAY,CAAA;AAExC,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,GAAG,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAO/E;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAoBzC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW;IA8B1C;;;;;OAKG;aACY,EAAE,eAAe,MAAM,WAAW,MAAM,mBAAmB,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;YAmCzF,EAAE,eAAe,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;;cAIrC,EAAE,eAAe,MAAM,WAAW,MAAM,SAAS,MAAM,UAAU,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;eAmBxF,EAAE,aACK,SAAS,eACP,MAAM,SACZ;QACL,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;QACvB,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KACvB;;;;;;;;;;;;;;;;;;;eA2BG,EAAE,aACK,SAAS,eACP,MAAM,UACX,MAAM,SACP,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;IAWvE;;;;;;OAMG;aAEG,EAAE,aACK,SAAS,eACP,MAAM,UACX,MAAM,YACJ,MAAM,GAAG,IAAI,WACd,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;gBAqBN,EAAE,aAAa,SAAS,eAAe,MAAM,UAAU,MAAM,YAAY,OAAO;;;;;;;;;;;;;;;;;;;IAclG,iFAAiF;mBAC5D,EAAE,eAAe,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAehF,0FAA0F;kBACtE,EAAE,eAAe,MAAM,UAAU,MAAM;;;IAU3D;;;OAGG;gBACe,EAAE,eAAe,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;;IA0BzD,kFAAkF;cAClE,EAAE,eAAe,MAAM,UAAU,MAAM;;;IAOvD,gFAAgF;;EAGnF;AACD,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA"}