@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,358 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "73e5ffbb-8a14-4b41-bdfd-776e51bec7d7",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"mod_quire.pages": {
|
|
8
|
+
"name": "pages",
|
|
9
|
+
"schema": "mod_quire",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "uuid",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true,
|
|
16
|
+
"default": "uuidv7()"
|
|
17
|
+
},
|
|
18
|
+
"workspace_id": {
|
|
19
|
+
"name": "workspace_id",
|
|
20
|
+
"type": "uuid",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": true
|
|
23
|
+
},
|
|
24
|
+
"space_id": {
|
|
25
|
+
"name": "space_id",
|
|
26
|
+
"type": "uuid",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true
|
|
29
|
+
},
|
|
30
|
+
"parent_id": {
|
|
31
|
+
"name": "parent_id",
|
|
32
|
+
"type": "uuid",
|
|
33
|
+
"primaryKey": false,
|
|
34
|
+
"notNull": false
|
|
35
|
+
},
|
|
36
|
+
"position": {
|
|
37
|
+
"name": "position",
|
|
38
|
+
"type": "text",
|
|
39
|
+
"primaryKey": false,
|
|
40
|
+
"notNull": true
|
|
41
|
+
},
|
|
42
|
+
"kind": {
|
|
43
|
+
"name": "kind",
|
|
44
|
+
"type": "text",
|
|
45
|
+
"primaryKey": false,
|
|
46
|
+
"notNull": true,
|
|
47
|
+
"default": "'page'"
|
|
48
|
+
},
|
|
49
|
+
"title": {
|
|
50
|
+
"name": "title",
|
|
51
|
+
"type": "text",
|
|
52
|
+
"primaryKey": false,
|
|
53
|
+
"notNull": true,
|
|
54
|
+
"default": "''"
|
|
55
|
+
},
|
|
56
|
+
"icon": {
|
|
57
|
+
"name": "icon",
|
|
58
|
+
"type": "text",
|
|
59
|
+
"primaryKey": false,
|
|
60
|
+
"notNull": false
|
|
61
|
+
},
|
|
62
|
+
"cover_url": {
|
|
63
|
+
"name": "cover_url",
|
|
64
|
+
"type": "text",
|
|
65
|
+
"primaryKey": false,
|
|
66
|
+
"notNull": false
|
|
67
|
+
},
|
|
68
|
+
"published_version_id": {
|
|
69
|
+
"name": "published_version_id",
|
|
70
|
+
"type": "uuid",
|
|
71
|
+
"primaryKey": false,
|
|
72
|
+
"notNull": false
|
|
73
|
+
},
|
|
74
|
+
"has_unpublished_changes": {
|
|
75
|
+
"name": "has_unpublished_changes",
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"primaryKey": false,
|
|
78
|
+
"notNull": true,
|
|
79
|
+
"default": false
|
|
80
|
+
},
|
|
81
|
+
"text": {
|
|
82
|
+
"name": "text",
|
|
83
|
+
"type": "text",
|
|
84
|
+
"primaryKey": false,
|
|
85
|
+
"notNull": true,
|
|
86
|
+
"default": "''"
|
|
87
|
+
},
|
|
88
|
+
"created_by": {
|
|
89
|
+
"name": "created_by",
|
|
90
|
+
"type": "uuid",
|
|
91
|
+
"primaryKey": false,
|
|
92
|
+
"notNull": false
|
|
93
|
+
},
|
|
94
|
+
"updated_by": {
|
|
95
|
+
"name": "updated_by",
|
|
96
|
+
"type": "uuid",
|
|
97
|
+
"primaryKey": false,
|
|
98
|
+
"notNull": false
|
|
99
|
+
},
|
|
100
|
+
"created_at": {
|
|
101
|
+
"name": "created_at",
|
|
102
|
+
"type": "timestamp with time zone",
|
|
103
|
+
"primaryKey": false,
|
|
104
|
+
"notNull": true,
|
|
105
|
+
"default": "now()"
|
|
106
|
+
},
|
|
107
|
+
"updated_at": {
|
|
108
|
+
"name": "updated_at",
|
|
109
|
+
"type": "timestamp with time zone",
|
|
110
|
+
"primaryKey": false,
|
|
111
|
+
"notNull": true,
|
|
112
|
+
"default": "now()"
|
|
113
|
+
},
|
|
114
|
+
"archived_at": {
|
|
115
|
+
"name": "archived_at",
|
|
116
|
+
"type": "timestamp with time zone",
|
|
117
|
+
"primaryKey": false,
|
|
118
|
+
"notNull": false
|
|
119
|
+
},
|
|
120
|
+
"deleted_at": {
|
|
121
|
+
"name": "deleted_at",
|
|
122
|
+
"type": "timestamp with time zone",
|
|
123
|
+
"primaryKey": false,
|
|
124
|
+
"notNull": false
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"indexes": {
|
|
128
|
+
"pages_ws_space_idx": {
|
|
129
|
+
"name": "pages_ws_space_idx",
|
|
130
|
+
"columns": [
|
|
131
|
+
{
|
|
132
|
+
"expression": "workspace_id",
|
|
133
|
+
"isExpression": false,
|
|
134
|
+
"asc": true,
|
|
135
|
+
"nulls": "last"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"expression": "space_id",
|
|
139
|
+
"isExpression": false,
|
|
140
|
+
"asc": true,
|
|
141
|
+
"nulls": "last"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"expression": "position",
|
|
145
|
+
"isExpression": false,
|
|
146
|
+
"asc": true,
|
|
147
|
+
"nulls": "last"
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"isUnique": false,
|
|
151
|
+
"concurrently": false,
|
|
152
|
+
"method": "btree",
|
|
153
|
+
"with": {}
|
|
154
|
+
},
|
|
155
|
+
"pages_ws_parent_idx": {
|
|
156
|
+
"name": "pages_ws_parent_idx",
|
|
157
|
+
"columns": [
|
|
158
|
+
{
|
|
159
|
+
"expression": "workspace_id",
|
|
160
|
+
"isExpression": false,
|
|
161
|
+
"asc": true,
|
|
162
|
+
"nulls": "last"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"expression": "parent_id",
|
|
166
|
+
"isExpression": false,
|
|
167
|
+
"asc": true,
|
|
168
|
+
"nulls": "last"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"expression": "position",
|
|
172
|
+
"isExpression": false,
|
|
173
|
+
"asc": true,
|
|
174
|
+
"nulls": "last"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"isUnique": false,
|
|
178
|
+
"concurrently": false,
|
|
179
|
+
"method": "btree",
|
|
180
|
+
"with": {}
|
|
181
|
+
},
|
|
182
|
+
"pages_ws_updated_idx": {
|
|
183
|
+
"name": "pages_ws_updated_idx",
|
|
184
|
+
"columns": [
|
|
185
|
+
{
|
|
186
|
+
"expression": "workspace_id",
|
|
187
|
+
"isExpression": false,
|
|
188
|
+
"asc": true,
|
|
189
|
+
"nulls": "last"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"expression": "updated_at",
|
|
193
|
+
"isExpression": false,
|
|
194
|
+
"asc": true,
|
|
195
|
+
"nulls": "last"
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"isUnique": false,
|
|
199
|
+
"concurrently": false,
|
|
200
|
+
"method": "btree",
|
|
201
|
+
"with": {}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"foreignKeys": {},
|
|
205
|
+
"compositePrimaryKeys": {},
|
|
206
|
+
"uniqueConstraints": {},
|
|
207
|
+
"policies": {},
|
|
208
|
+
"checkConstraints": {},
|
|
209
|
+
"isRLSEnabled": false
|
|
210
|
+
},
|
|
211
|
+
"mod_quire.spaces": {
|
|
212
|
+
"name": "spaces",
|
|
213
|
+
"schema": "mod_quire",
|
|
214
|
+
"columns": {
|
|
215
|
+
"id": {
|
|
216
|
+
"name": "id",
|
|
217
|
+
"type": "uuid",
|
|
218
|
+
"primaryKey": true,
|
|
219
|
+
"notNull": true,
|
|
220
|
+
"default": "uuidv7()"
|
|
221
|
+
},
|
|
222
|
+
"workspace_id": {
|
|
223
|
+
"name": "workspace_id",
|
|
224
|
+
"type": "uuid",
|
|
225
|
+
"primaryKey": false,
|
|
226
|
+
"notNull": true
|
|
227
|
+
},
|
|
228
|
+
"key": {
|
|
229
|
+
"name": "key",
|
|
230
|
+
"type": "text",
|
|
231
|
+
"primaryKey": false,
|
|
232
|
+
"notNull": true
|
|
233
|
+
},
|
|
234
|
+
"name": {
|
|
235
|
+
"name": "name",
|
|
236
|
+
"type": "text",
|
|
237
|
+
"primaryKey": false,
|
|
238
|
+
"notNull": true
|
|
239
|
+
},
|
|
240
|
+
"description": {
|
|
241
|
+
"name": "description",
|
|
242
|
+
"type": "text",
|
|
243
|
+
"primaryKey": false,
|
|
244
|
+
"notNull": true,
|
|
245
|
+
"default": "''"
|
|
246
|
+
},
|
|
247
|
+
"icon": {
|
|
248
|
+
"name": "icon",
|
|
249
|
+
"type": "text",
|
|
250
|
+
"primaryKey": false,
|
|
251
|
+
"notNull": false
|
|
252
|
+
},
|
|
253
|
+
"visibility": {
|
|
254
|
+
"name": "visibility",
|
|
255
|
+
"type": "text",
|
|
256
|
+
"primaryKey": false,
|
|
257
|
+
"notNull": true,
|
|
258
|
+
"default": "'open'"
|
|
259
|
+
},
|
|
260
|
+
"homepage_id": {
|
|
261
|
+
"name": "homepage_id",
|
|
262
|
+
"type": "uuid",
|
|
263
|
+
"primaryKey": false,
|
|
264
|
+
"notNull": false
|
|
265
|
+
},
|
|
266
|
+
"created_by": {
|
|
267
|
+
"name": "created_by",
|
|
268
|
+
"type": "uuid",
|
|
269
|
+
"primaryKey": false,
|
|
270
|
+
"notNull": false
|
|
271
|
+
},
|
|
272
|
+
"created_at": {
|
|
273
|
+
"name": "created_at",
|
|
274
|
+
"type": "timestamp with time zone",
|
|
275
|
+
"primaryKey": false,
|
|
276
|
+
"notNull": true,
|
|
277
|
+
"default": "now()"
|
|
278
|
+
},
|
|
279
|
+
"updated_at": {
|
|
280
|
+
"name": "updated_at",
|
|
281
|
+
"type": "timestamp with time zone",
|
|
282
|
+
"primaryKey": false,
|
|
283
|
+
"notNull": true,
|
|
284
|
+
"default": "now()"
|
|
285
|
+
},
|
|
286
|
+
"archived_at": {
|
|
287
|
+
"name": "archived_at",
|
|
288
|
+
"type": "timestamp with time zone",
|
|
289
|
+
"primaryKey": false,
|
|
290
|
+
"notNull": false
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
"indexes": {
|
|
294
|
+
"spaces_ws_key_uq": {
|
|
295
|
+
"name": "spaces_ws_key_uq",
|
|
296
|
+
"columns": [
|
|
297
|
+
{
|
|
298
|
+
"expression": "workspace_id",
|
|
299
|
+
"isExpression": false,
|
|
300
|
+
"asc": true,
|
|
301
|
+
"nulls": "last"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"expression": "key",
|
|
305
|
+
"isExpression": false,
|
|
306
|
+
"asc": true,
|
|
307
|
+
"nulls": "last"
|
|
308
|
+
}
|
|
309
|
+
],
|
|
310
|
+
"isUnique": true,
|
|
311
|
+
"concurrently": false,
|
|
312
|
+
"method": "btree",
|
|
313
|
+
"with": {}
|
|
314
|
+
},
|
|
315
|
+
"spaces_ws_idx": {
|
|
316
|
+
"name": "spaces_ws_idx",
|
|
317
|
+
"columns": [
|
|
318
|
+
{
|
|
319
|
+
"expression": "workspace_id",
|
|
320
|
+
"isExpression": false,
|
|
321
|
+
"asc": true,
|
|
322
|
+
"nulls": "last"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"expression": "created_at",
|
|
326
|
+
"isExpression": false,
|
|
327
|
+
"asc": true,
|
|
328
|
+
"nulls": "last"
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"isUnique": false,
|
|
332
|
+
"concurrently": false,
|
|
333
|
+
"method": "btree",
|
|
334
|
+
"with": {}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"foreignKeys": {},
|
|
338
|
+
"compositePrimaryKeys": {},
|
|
339
|
+
"uniqueConstraints": {},
|
|
340
|
+
"policies": {},
|
|
341
|
+
"checkConstraints": {},
|
|
342
|
+
"isRLSEnabled": false
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
"enums": {},
|
|
346
|
+
"schemas": {
|
|
347
|
+
"mod_quire": "mod_quire"
|
|
348
|
+
},
|
|
349
|
+
"sequences": {},
|
|
350
|
+
"roles": {},
|
|
351
|
+
"policies": {},
|
|
352
|
+
"views": {},
|
|
353
|
+
"_meta": {
|
|
354
|
+
"columns": {},
|
|
355
|
+
"schemas": {},
|
|
356
|
+
"tables": {}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "7",
|
|
3
|
+
"dialect": "postgresql",
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"idx": 0,
|
|
7
|
+
"version": "7",
|
|
8
|
+
"when": 1787566823449,
|
|
9
|
+
"tag": "0000_init",
|
|
10
|
+
"breakpoints": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"idx": 1,
|
|
14
|
+
"version": "7",
|
|
15
|
+
"when": 1787566824449,
|
|
16
|
+
"tag": "0001_rls",
|
|
17
|
+
"breakpoints": true
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kernhq/module-quire",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Kern Quire: collaborative documents, spaces and page trees",
|
|
5
|
+
"homepage": "https://kernaio.com",
|
|
6
|
+
"license": "AGPL-3.0-only",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/KernAIO/modules",
|
|
11
|
+
"directory": "packages/quire"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"migrations",
|
|
19
|
+
"src/client",
|
|
20
|
+
"src/contract"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
"./contract": {
|
|
24
|
+
"types": "./dist/contract/index.d.ts",
|
|
25
|
+
"import": "./dist/contract/index.js",
|
|
26
|
+
"default": "./dist/contract/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./server": {
|
|
29
|
+
"types": "./dist/server/index.d.ts",
|
|
30
|
+
"import": "./dist/server/index.js",
|
|
31
|
+
"default": "./dist/server/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./client": {
|
|
34
|
+
"types": "./src/client/index.ts",
|
|
35
|
+
"svelte": "./src/client/index.ts",
|
|
36
|
+
"default": "./src/client/index.ts"
|
|
37
|
+
},
|
|
38
|
+
"./migrations": "./migrations",
|
|
39
|
+
"./client/*": {
|
|
40
|
+
"types": "./src/client/*",
|
|
41
|
+
"svelte": "./src/client/*",
|
|
42
|
+
"default": "./src/client/*"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@kernhq/contracts": "^0.5.0",
|
|
47
|
+
"@kernhq/kernel": "^0.6.0",
|
|
48
|
+
"@kernhq/sdk": "^0.1.0",
|
|
49
|
+
"@orpc/contract": "^1.15.0",
|
|
50
|
+
"@orpc/server": "^1.15.0",
|
|
51
|
+
"drizzle-orm": "^0.45.0",
|
|
52
|
+
"zod": "^4.1.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"svelte": "^5.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"svelte": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@kernhq/tsconfig": "^0.1.0",
|
|
64
|
+
"@types/node": "^24.0.0",
|
|
65
|
+
"@types/pg": "^8.15.0",
|
|
66
|
+
"drizzle-kit": "^0.31.0",
|
|
67
|
+
"pg": "^8.16.0",
|
|
68
|
+
"typescript": "~5.9.3",
|
|
69
|
+
"vitest": "^4.0.0"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsc -p tsconfig.json",
|
|
73
|
+
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
74
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
75
|
+
"test": "vitest run",
|
|
76
|
+
"db:generate": "drizzle-kit generate"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createModuleClient, type KernClientOptions } from '@kernhq/sdk'
|
|
2
|
+
import type { ContractRouterClient } from '@orpc/contract'
|
|
3
|
+
import type { PageNode, QuireContract } from '../contract/index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The client half.
|
|
7
|
+
*
|
|
8
|
+
* Published as **source**, not compiled: the consumer builds the TypeScript and Svelte with its own
|
|
9
|
+
* toolchain, which is what lets `$state` in a module store stay reactive inside the app. Two
|
|
10
|
+
* consequences worth knowing before you edit anything here — nothing in this package compiles it,
|
|
11
|
+
* so `pnpm build` passes over a syntax error and only the app finds it; and `files` in package.json
|
|
12
|
+
* must cover every directory this entry reaches, contract source included.
|
|
13
|
+
*
|
|
14
|
+
* What lives here: the typed API client, and any logic that is about this module but not about a
|
|
15
|
+
* screen (formatting, grouping, parsing). What does not: the `defineClientModule` manifest and the
|
|
16
|
+
* Svelte components, which live in the app so their labels can go through its message catalogue.
|
|
17
|
+
* `pnpm new-module` generates both halves.
|
|
18
|
+
*/
|
|
19
|
+
export type QuireApi = ContractRouterClient<QuireContract>
|
|
20
|
+
|
|
21
|
+
export function createQuireClient(opts: KernClientOptions): QuireApi {
|
|
22
|
+
return createModuleClient<QuireApi>(opts, 'quire')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
MODULE_ID,
|
|
27
|
+
type Page,
|
|
28
|
+
type PageKind,
|
|
29
|
+
type PageNode,
|
|
30
|
+
quirePermissions,
|
|
31
|
+
type Space,
|
|
32
|
+
type SpaceVisibility,
|
|
33
|
+
} from '../contract/index.js'
|
|
34
|
+
export * from './rank.js'
|
|
35
|
+
|
|
36
|
+
/** The permission keys, so the app gates on a constant rather than a string it retyped. */
|
|
37
|
+
export const QUIRE_PERMISSIONS = {
|
|
38
|
+
spaceView: 'quire.space.view',
|
|
39
|
+
spaceManage: 'quire.space.manage',
|
|
40
|
+
pageView: 'quire.page.view',
|
|
41
|
+
pageCreate: 'quire.page.create',
|
|
42
|
+
pageEdit: 'quire.page.edit',
|
|
43
|
+
pageDelete: 'quire.page.delete',
|
|
44
|
+
} as const
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build the sidebar tree from the flat, position-ordered list `pages.tree` returns.
|
|
48
|
+
*
|
|
49
|
+
* Isomorphic on purpose: the app draws it and the published-site renderer will need the same shape,
|
|
50
|
+
* and neither should re-derive it. Rows whose parent is missing — because it was archived and this
|
|
51
|
+
* caller asked for the live tree — are lifted to the top rather than dropped, so a page is never
|
|
52
|
+
* invisible because of where it happens to sit.
|
|
53
|
+
*/
|
|
54
|
+
export interface PageTreeNode extends PageNode {
|
|
55
|
+
children: PageTreeNode[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildPageTree(nodes: readonly PageNode[]): PageTreeNode[] {
|
|
59
|
+
const byId = new Map<string, PageTreeNode>(nodes.map((n) => [n.id, { ...n, children: [] }]))
|
|
60
|
+
const roots: PageTreeNode[] = []
|
|
61
|
+
for (const node of byId.values()) {
|
|
62
|
+
const parent = node.parentId ? byId.get(node.parentId) : undefined
|
|
63
|
+
if (parent) parent.children.push(node)
|
|
64
|
+
else roots.push(node)
|
|
65
|
+
}
|
|
66
|
+
return roots
|
|
67
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ordering invariants a page tree depends on.
|
|
3
|
+
*
|
|
4
|
+
* This algorithm is duplicated from the tracker on purpose — modules do not import one another — so
|
|
5
|
+
* it carries its own test rather than trusting that the original still holds.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it } from 'vitest'
|
|
8
|
+
import { initialRank, isValidRank, rankBetween, rankSequence } from './rank.js'
|
|
9
|
+
|
|
10
|
+
describe('rankBetween', () => {
|
|
11
|
+
it('lands strictly between its neighbours', () => {
|
|
12
|
+
const a = initialRank()
|
|
13
|
+
const c = rankBetween(a, null)
|
|
14
|
+
const b = rankBetween(a, c)
|
|
15
|
+
expect(a < b).toBe(true)
|
|
16
|
+
expect(b < c).toBe(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('opens either end', () => {
|
|
20
|
+
const first = initialRank()
|
|
21
|
+
expect(rankBetween(null, first) < first).toBe(true)
|
|
22
|
+
expect(rankBetween(first, null) > first).toBe(true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('refuses neighbours that are the wrong way round', () => {
|
|
26
|
+
const a = initialRank()
|
|
27
|
+
const b = rankBetween(a, null)
|
|
28
|
+
expect(() => rankBetween(b, a)).toThrow()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('keeps finding room, however many times the same gap is split', () => {
|
|
32
|
+
// The failure this guards against is a key that runs out of precision and starts colliding, so
|
|
33
|
+
// two pages dropped in the same place end up in an order nobody chose.
|
|
34
|
+
let lo = initialRank()
|
|
35
|
+
const hi = rankBetween(lo, null)
|
|
36
|
+
const seen = new Set<string>([lo, hi])
|
|
37
|
+
for (let i = 0; i < 200; i++) {
|
|
38
|
+
const mid = rankBetween(lo, hi)
|
|
39
|
+
expect(lo < mid, `${lo} < ${mid} failed at split ${i}`).toBe(true)
|
|
40
|
+
expect(mid < hi, `${mid} < ${hi} failed at split ${i}`).toBe(true)
|
|
41
|
+
expect(seen.has(mid), `collision at split ${i}`).toBe(false)
|
|
42
|
+
expect(isValidRank(mid)).toBe(true)
|
|
43
|
+
seen.add(mid)
|
|
44
|
+
lo = mid
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('never mints a key ending in the lowest digit, so there is always room below', () => {
|
|
49
|
+
let prev: string | null = null
|
|
50
|
+
for (let i = 0; i < 50; i++) {
|
|
51
|
+
const next: string = rankBetween(prev, null)
|
|
52
|
+
expect(isValidRank(next)).toBe(true)
|
|
53
|
+
prev = next
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('inserting at the front, over and over', () => {
|
|
59
|
+
it('keeps finding room instead of looping until the process dies', () => {
|
|
60
|
+
// The regression this guards: an absent lower bound was treated as "below digit 0", so the
|
|
61
|
+
// fifth insertion at the top of a list minted '0', and the sixth searched for something below
|
|
62
|
+
// it for ever — growing a string until the heap gave out. It took five pages created at the top
|
|
63
|
+
// of one space to hit it.
|
|
64
|
+
let first = initialRank()
|
|
65
|
+
const seen = new Set<string>([first])
|
|
66
|
+
for (let i = 0; i < 200; i++) {
|
|
67
|
+
const next = rankBetween(null, first)
|
|
68
|
+
expect(next < first, `${next} < ${first} failed at insertion ${i}`).toBe(true)
|
|
69
|
+
expect(isValidRank(next), `${next} is not a valid key at insertion ${i}`).toBe(true)
|
|
70
|
+
expect(seen.has(next), `collision at insertion ${i}`).toBe(false)
|
|
71
|
+
seen.add(next)
|
|
72
|
+
first = next
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('never mints a key with no room below it', () => {
|
|
77
|
+
let first = initialRank()
|
|
78
|
+
for (let i = 0; i < 50; i++) {
|
|
79
|
+
first = rankBetween(null, first)
|
|
80
|
+
expect(first.endsWith('0'), `${first} ends in the lowest digit`).toBe(false)
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('rankSequence', () => {
|
|
86
|
+
it('produces keys that already sort into the order they were asked for', () => {
|
|
87
|
+
const keys = rankSequence(25)
|
|
88
|
+
expect(keys).toHaveLength(25)
|
|
89
|
+
expect([...keys].sort()).toEqual(keys)
|
|
90
|
+
expect(new Set(keys).size).toBe(25)
|
|
91
|
+
})
|
|
92
|
+
})
|