@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.
- package/LICENSE +662 -0
- package/README.md +71 -0
- package/dist/client/rank.d.ts +42 -0
- package/dist/client/rank.d.ts.map +1 -0
- package/dist/client/rank.js +97 -0
- package/dist/client/rank.js.map +1 -0
- package/dist/contract/capabilities.d.ts +35 -0
- package/dist/contract/capabilities.d.ts.map +1 -0
- package/dist/contract/capabilities.js +35 -0
- package/dist/contract/capabilities.js.map +1 -0
- package/dist/contract/events.d.ts +58 -0
- package/dist/contract/events.d.ts.map +1 -0
- package/dist/contract/events.js +18 -0
- package/dist/contract/events.js.map +1 -0
- package/dist/contract/index.d.ts +6 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +6 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/models.d.ts +95 -0
- package/dist/contract/models.d.ts.map +1 -0
- package/dist/contract/models.js +82 -0
- package/dist/contract/models.js.map +1 -0
- package/dist/contract/permissions.d.ts +51 -0
- package/dist/contract/permissions.d.ts.map +1 -0
- package/dist/contract/permissions.js +59 -0
- package/dist/contract/permissions.js.map +1 -0
- package/dist/contract/router.d.ts +713 -0
- package/dist/contract/router.d.ts.map +1 -0
- package/dist/contract/router.js +109 -0
- package/dist/contract/router.js.map +1 -0
- package/dist/server/_impl.d.ts +815 -0
- package/dist/server/_impl.d.ts.map +1 -0
- package/dist/server/_impl.js +189 -0
- package/dist/server/_impl.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +87 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/schema.d.ts +540 -0
- package/dist/server/schema.d.ts.map +1 -0
- package/dist/server/schema.js +86 -0
- package/dist/server/schema.js.map +1 -0
- package/dist/server/services/access.d.ts +78 -0
- package/dist/server/services/access.d.ts.map +1 -0
- package/dist/server/services/access.js +96 -0
- package/dist/server/services/access.js.map +1 -0
- package/dist/server/services/index.d.ts +15 -0
- package/dist/server/services/index.d.ts.map +1 -0
- package/dist/server/services/index.js +22 -0
- package/dist/server/services/index.js.map +1 -0
- package/dist/server/services/pages.d.ts +190 -0
- package/dist/server/services/pages.d.ts.map +1 -0
- package/dist/server/services/pages.js +242 -0
- package/dist/server/services/pages.js.map +1 -0
- package/dist/server/services/spaces.d.ts +95 -0
- package/dist/server/services/spaces.d.ts.map +1 -0
- package/dist/server/services/spaces.js +100 -0
- package/dist/server/services/spaces.js.map +1 -0
- package/migrations/0000_init.sql +43 -0
- package/migrations/0001_rls.sql +17 -0
- package/migrations/meta/0000_snapshot.json +358 -0
- package/migrations/meta/_journal.json +20 -0
- package/package.json +78 -0
- package/src/client/index.ts +67 -0
- package/src/client/rank.test.ts +92 -0
- package/src/client/rank.ts +100 -0
- package/src/contract/capabilities.ts +36 -0
- package/src/contract/events.ts +22 -0
- package/src/contract/index.ts +5 -0
- package/src/contract/models.ts +93 -0
- package/src/contract/permissions.ts +59 -0
- 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"}
|