@kernhq/module-quire 0.4.0 → 0.5.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.
@@ -29,6 +29,13 @@
29
29
  "when": 1787600694344,
30
30
  "tag": "0003_comments",
31
31
  "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "7",
36
+ "when": 1787601921354,
37
+ "tag": "0004_databases",
38
+ "breakpoints": true
32
39
  }
33
40
  ]
34
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernhq/module-quire",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Kern Quire: collaborative documents, spaces and page trees",
5
5
  "homepage": "https://kernaio.com",
6
6
  "license": "AGPL-3.0-only",
@@ -24,6 +24,9 @@ export function createQuireClient(opts: KernClientOptions): QuireApi {
24
24
  }
25
25
 
26
26
  export {
27
+ type Comment,
28
+ type CommentAnchor,
29
+ type CommentThread,
27
30
  MODULE_ID,
28
31
  type Page,
29
32
  type PageKind,
@@ -3,4 +3,5 @@ export * from './events.js'
3
3
  export * from './models.js'
4
4
  export * from './notifications.js'
5
5
  export * from './permissions.js'
6
+ export * from './properties.js'
6
7
  export * from './router.js'
@@ -0,0 +1,199 @@
1
+ import { Id, Timestamp, UserId } from '@kernhq/contracts'
2
+ import { z } from 'zod'
3
+
4
+ /**
5
+ * What a column can be.
6
+ *
7
+ * The set is closed on purpose. A property type is not just a input widget: it decides how a value
8
+ * sorts, what a filter can ask of it, and whether a rollup can add it up. An open set would mean a
9
+ * table view that can display something no filter can find.
10
+ */
11
+ export const PropertyType = z.enum([
12
+ 'text',
13
+ 'number',
14
+ 'select',
15
+ 'multi_select',
16
+ 'status',
17
+ 'date',
18
+ 'person',
19
+ 'files',
20
+ 'checkbox',
21
+ 'url',
22
+ 'email',
23
+ 'phone',
24
+ 'relation',
25
+ 'rollup',
26
+ 'formula',
27
+ 'created_time',
28
+ 'created_by',
29
+ 'edited_time',
30
+ 'edited_by',
31
+ ])
32
+ export type PropertyType = z.infer<typeof PropertyType>
33
+
34
+ /** A choice in a select, a multi-select or a status. */
35
+ export const SelectOption = z.object({
36
+ id: z.string().min(1).max(64),
37
+ label: z.string().min(1).max(120),
38
+ colour: z.string().max(32).default('slate'),
39
+ /** status only: which band of the workflow this sits in */
40
+ group: z.enum(['todo', 'doing', 'done']).optional(),
41
+ })
42
+ export type SelectOption = z.infer<typeof SelectOption>
43
+
44
+ /** How a rollup reduces the values it gathers from the other side of a relation. */
45
+ export const RollupFunction = z.enum([
46
+ 'count',
47
+ 'count_values',
48
+ 'count_unique',
49
+ 'sum',
50
+ 'average',
51
+ 'min',
52
+ 'max',
53
+ 'range',
54
+ 'show_original',
55
+ 'checked',
56
+ 'unchecked',
57
+ 'percent_checked',
58
+ ])
59
+ export type RollupFunction = z.infer<typeof RollupFunction>
60
+
61
+ /**
62
+ * Everything a type needs beyond its name.
63
+ *
64
+ * One permissive object rather than a discriminated union per type: the union would have to be
65
+ * exhaustive at every boundary, and a column's configuration is read by code that already knows
66
+ * which type it is holding. What matters is that adding a type never needs a migration.
67
+ */
68
+ export const PropertyConfig = z.object({
69
+ options: z.array(SelectOption).optional(),
70
+ /** number: how it is drawn — plain, a percentage, a currency, a bar */
71
+ format: z.string().max(32).optional(),
72
+ precision: z.number().int().min(0).max(8).optional(),
73
+ /** date: whether the value carries a time, and whether it is a range */
74
+ includeTime: z.boolean().optional(),
75
+ isRange: z.boolean().optional(),
76
+ /** relation: the database on the other side, and the property that points back */
77
+ relationDatabaseId: Id.optional(),
78
+ relationPropertyId: Id.optional(),
79
+ /** rollup: which relation to walk, which property to gather, and how to reduce it */
80
+ rollupRelationPropertyId: Id.optional(),
81
+ rollupTargetPropertyId: Id.optional(),
82
+ rollupFunction: RollupFunction.optional(),
83
+ /** formula: the expression, exactly as somebody typed it */
84
+ expression: z.string().max(4000).optional(),
85
+ /** person: whether more than one may be chosen */
86
+ multiple: z.boolean().optional(),
87
+ })
88
+ export type PropertyConfig = z.infer<typeof PropertyConfig>
89
+
90
+ export const Property = z.object({
91
+ id: Id,
92
+ databaseId: Id,
93
+ key: z.string().min(1).max(64),
94
+ name: z.string().min(1).max(120),
95
+ type: PropertyType,
96
+ config: PropertyConfig,
97
+ position: z.string(),
98
+ hidden: z.boolean(),
99
+ })
100
+ export type Property = z.infer<typeof Property>
101
+
102
+ /**
103
+ * A cell.
104
+ *
105
+ * Deliberately loose: the shape depends on the column's type, and the server validates a value
106
+ * against its own property before it is written. Typing it here as a union would put the same
107
+ * exhaustive switch in every consumer that only ever renders one type at a time.
108
+ */
109
+ export const PropertyValue = z.unknown()
110
+
111
+ export const ViewKind = z.enum(['table', 'board', 'calendar', 'gallery', 'list', 'timeline'])
112
+ export type ViewKind = z.infer<typeof ViewKind>
113
+
114
+ /** How a filter compares. Which of these a column accepts depends on its type. */
115
+ export const FilterOperator = z.enum([
116
+ 'equals',
117
+ 'not_equals',
118
+ 'contains',
119
+ 'not_contains',
120
+ 'starts_with',
121
+ 'ends_with',
122
+ 'is_empty',
123
+ 'is_not_empty',
124
+ 'greater_than',
125
+ 'less_than',
126
+ 'on_or_before',
127
+ 'on_or_after',
128
+ 'is_any_of',
129
+ 'is_none_of',
130
+ ])
131
+ export type FilterOperator = z.infer<typeof FilterOperator>
132
+
133
+ export const Filter = z.object({
134
+ propertyKey: z.string(),
135
+ operator: FilterOperator,
136
+ value: z.unknown().optional(),
137
+ })
138
+
139
+ export const Sort = z.object({
140
+ propertyKey: z.string(),
141
+ direction: z.enum(['asc', 'desc']).default('asc'),
142
+ })
143
+
144
+ export const ViewConfig = z.object({
145
+ filters: z.array(Filter).default([]),
146
+ /** every filter must hold, or any one of them */
147
+ filterMode: z.enum(['and', 'or']).default('and'),
148
+ sorts: z.array(Sort).default([]),
149
+ /** board: which property makes the columns; calendar: which date is plotted */
150
+ groupBy: z.string().nullable().default(null),
151
+ dateProperty: z.string().nullable().default(null),
152
+ visibleProperties: z.array(z.string()).nullable().default(null),
153
+ columnWidths: z.record(z.string(), z.number()).default({}),
154
+ cardSize: z.enum(['small', 'medium', 'large']).default('medium'),
155
+ /** gallery: which files property provides the picture */
156
+ coverProperty: z.string().nullable().default(null),
157
+ })
158
+ export type ViewConfig = z.infer<typeof ViewConfig>
159
+
160
+ export const View = z.object({
161
+ id: Id,
162
+ databaseId: Id,
163
+ name: z.string().min(1).max(120),
164
+ kind: ViewKind,
165
+ config: ViewConfig,
166
+ position: z.string(),
167
+ isDefault: z.boolean(),
168
+ })
169
+ export type View = z.infer<typeof View>
170
+
171
+ export const Database = z.object({
172
+ id: Id,
173
+ workspaceId: z.string(),
174
+ spaceId: Id,
175
+ pageId: Id,
176
+ name: z.string(),
177
+ description: z.string(),
178
+ inline: z.boolean(),
179
+ properties: z.array(Property),
180
+ views: z.array(View),
181
+ createdAt: Timestamp,
182
+ updatedAt: Timestamp,
183
+ })
184
+ export type Database = z.infer<typeof Database>
185
+
186
+ /** A row: the page, plus its cells and whatever the server computed from them. */
187
+ export const Row = z.object({
188
+ id: Id,
189
+ databaseId: Id,
190
+ title: z.string(),
191
+ icon: z.string().nullable(),
192
+ props: z.record(z.string(), PropertyValue),
193
+ computed: z.record(z.string(), PropertyValue),
194
+ createdBy: UserId.nullable(),
195
+ updatedBy: UserId.nullable(),
196
+ createdAt: Timestamp,
197
+ updatedAt: Timestamp,
198
+ })
199
+ export type Row = z.infer<typeof Row>