@kernhq/module-quire 0.10.8 → 0.11.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 (53) hide show
  1. package/dist/contract/models.d.ts +87 -0
  2. package/dist/contract/models.d.ts.map +1 -1
  3. package/dist/contract/models.js +73 -0
  4. package/dist/contract/models.js.map +1 -1
  5. package/dist/contract/permissions.d.ts.map +1 -1
  6. package/dist/contract/permissions.js +32 -0
  7. package/dist/contract/permissions.js.map +1 -1
  8. package/dist/contract/router.d.ts +645 -0
  9. package/dist/contract/router.d.ts.map +1 -1
  10. package/dist/contract/router.js +137 -2
  11. package/dist/contract/router.js.map +1 -1
  12. package/dist/server/_impl.d.ts +877 -0
  13. package/dist/server/_impl.d.ts.map +1 -1
  14. package/dist/server/_impl.js +127 -1
  15. package/dist/server/_impl.js.map +1 -1
  16. package/dist/server/schema.d.ts +482 -1
  17. package/dist/server/schema.d.ts.map +1 -1
  18. package/dist/server/schema.js +115 -1
  19. package/dist/server/schema.js.map +1 -1
  20. package/dist/server/services/index.d.ts +3 -0
  21. package/dist/server/services/index.d.ts.map +1 -1
  22. package/dist/server/services/index.js +3 -0
  23. package/dist/server/services/index.js.map +1 -1
  24. package/dist/server/services/organisation.d.ts +117 -0
  25. package/dist/server/services/organisation.d.ts.map +1 -0
  26. package/dist/server/services/organisation.js +319 -0
  27. package/dist/server/services/organisation.js.map +1 -0
  28. package/dist/server/services/pages.d.ts +16 -1
  29. package/dist/server/services/pages.d.ts.map +1 -1
  30. package/dist/server/services/pages.js +62 -3
  31. package/dist/server/services/pages.js.map +1 -1
  32. package/migrations/0007_organisation.sql +114 -0
  33. package/migrations/meta/0007_snapshot.json +1588 -0
  34. package/migrations/meta/_journal.json +7 -0
  35. package/package.json +1 -1
  36. package/src/client/components/ConfirmDialog.svelte +123 -0
  37. package/src/client/components/FavoriteStar.svelte +81 -0
  38. package/src/client/components/LabelChip.svelte +46 -0
  39. package/src/client/components/LabelManager.svelte +336 -0
  40. package/src/client/components/PageLabels.svelte +158 -0
  41. package/src/client/components/SidebarFavorites.svelte +262 -0
  42. package/src/client/components/SidebarRecents.svelte +98 -0
  43. package/src/client/components/SidebarSpaces.svelte +266 -1
  44. package/src/client/i18n.ts +387 -0
  45. package/src/client/index.ts +8 -0
  46. package/src/client/mock.ts +306 -0
  47. package/src/client/module.ts +18 -0
  48. package/src/client/pages/PageView.svelte +235 -5
  49. package/src/client/pages/TrashPage.svelte +378 -0
  50. package/src/client/query.ts +24 -0
  51. package/src/contract/models.ts +83 -0
  52. package/src/contract/permissions.ts +36 -0
  53. package/src/contract/router.ts +153 -1
@@ -1769,6 +1769,487 @@ export declare const relations: import("drizzle-orm/pg-core").PgTableWithColumns
1769
1769
  };
1770
1770
  dialect: "pg";
1771
1771
  }>;
1772
+ /**
1773
+ * A word a space puts on pages, so they can be gathered by something other than where they sit.
1774
+ *
1775
+ * Scoped to a space rather than to the workspace: two teams both wanting "Draft" should not have to
1776
+ * agree on what it means, and a label list that spans every space is a list nobody can read.
1777
+ *
1778
+ * The unique index is on `lower(name)`, not on `name`. Case is not a distinction anybody means here
1779
+ * — "Draft" and "draft" next to each other in a picker read as a mistake in the data, and whichever
1780
+ * one a person clicks is a coin toss. Lowercasing in the constraint rather than in the column keeps
1781
+ * the capitalisation somebody chose while refusing the near-duplicate.
1782
+ */
1783
+ export declare const labels: import("drizzle-orm/pg-core").PgTableWithColumns<{
1784
+ name: "labels";
1785
+ schema: "mod_quire";
1786
+ columns: {
1787
+ id: import("drizzle-orm/pg-core").PgColumn<{
1788
+ name: "id";
1789
+ tableName: "labels";
1790
+ dataType: "string";
1791
+ columnType: "PgUUID";
1792
+ data: string;
1793
+ driverParam: string;
1794
+ notNull: true;
1795
+ hasDefault: true;
1796
+ isPrimaryKey: true;
1797
+ isAutoincrement: false;
1798
+ hasRuntimeDefault: false;
1799
+ enumValues: undefined;
1800
+ baseColumn: never;
1801
+ identity: undefined;
1802
+ generated: undefined;
1803
+ }, {}, {}>;
1804
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
1805
+ name: "workspace_id";
1806
+ tableName: "labels";
1807
+ dataType: "string";
1808
+ columnType: "PgUUID";
1809
+ data: string;
1810
+ driverParam: string;
1811
+ notNull: true;
1812
+ hasDefault: false;
1813
+ isPrimaryKey: false;
1814
+ isAutoincrement: false;
1815
+ hasRuntimeDefault: false;
1816
+ enumValues: undefined;
1817
+ baseColumn: never;
1818
+ identity: undefined;
1819
+ generated: undefined;
1820
+ }, {}, {}>;
1821
+ spaceId: import("drizzle-orm/pg-core").PgColumn<{
1822
+ name: "space_id";
1823
+ tableName: "labels";
1824
+ dataType: "string";
1825
+ columnType: "PgUUID";
1826
+ data: string;
1827
+ driverParam: string;
1828
+ notNull: true;
1829
+ hasDefault: false;
1830
+ isPrimaryKey: false;
1831
+ isAutoincrement: false;
1832
+ hasRuntimeDefault: false;
1833
+ enumValues: undefined;
1834
+ baseColumn: never;
1835
+ identity: undefined;
1836
+ generated: undefined;
1837
+ }, {}, {}>;
1838
+ name: import("drizzle-orm/pg-core").PgColumn<{
1839
+ name: "name";
1840
+ tableName: "labels";
1841
+ dataType: "string";
1842
+ columnType: "PgText";
1843
+ data: string;
1844
+ driverParam: string;
1845
+ notNull: true;
1846
+ hasDefault: false;
1847
+ isPrimaryKey: false;
1848
+ isAutoincrement: false;
1849
+ hasRuntimeDefault: false;
1850
+ enumValues: [string, ...string[]];
1851
+ baseColumn: never;
1852
+ identity: undefined;
1853
+ generated: undefined;
1854
+ }, {}, {}>;
1855
+ colour: import("drizzle-orm/pg-core").PgColumn<{
1856
+ name: "colour";
1857
+ tableName: "labels";
1858
+ dataType: "string";
1859
+ columnType: "PgText";
1860
+ data: string;
1861
+ driverParam: string;
1862
+ notNull: true;
1863
+ hasDefault: true;
1864
+ isPrimaryKey: false;
1865
+ isAutoincrement: false;
1866
+ hasRuntimeDefault: false;
1867
+ enumValues: [string, ...string[]];
1868
+ baseColumn: never;
1869
+ identity: undefined;
1870
+ generated: undefined;
1871
+ }, {}, {}>;
1872
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
1873
+ name: string;
1874
+ tableName: "labels";
1875
+ dataType: "date";
1876
+ columnType: "PgTimestamp";
1877
+ data: Date;
1878
+ driverParam: string;
1879
+ notNull: true;
1880
+ hasDefault: true;
1881
+ isPrimaryKey: false;
1882
+ isAutoincrement: false;
1883
+ hasRuntimeDefault: false;
1884
+ enumValues: undefined;
1885
+ baseColumn: never;
1886
+ identity: undefined;
1887
+ generated: undefined;
1888
+ }, {}, {}>;
1889
+ };
1890
+ dialect: "pg";
1891
+ }>;
1892
+ /**
1893
+ * A label on a page.
1894
+ *
1895
+ * The primary key is composite and declared in the table's second argument. Two column-level
1896
+ * `.primaryKey()` calls are not a composite key — drizzle emits `PRIMARY KEY` on both columns and
1897
+ * Postgres refuses the table with "multiple primary keys for table are not allowed". Because the
1898
+ * kernel runs a module's migrations at boot, that is not a broken table but a host service that
1899
+ * never binds its port, taking every other module in the process down with it.
1900
+ *
1901
+ * `(page_id, label_id)` leads with the page because reading a page's labels is what happens on every
1902
+ * page load; the second index leads with the label, which is what a "everything tagged X" list asks.
1903
+ */
1904
+ export declare const pageLabels: import("drizzle-orm/pg-core").PgTableWithColumns<{
1905
+ name: "page_labels";
1906
+ schema: "mod_quire";
1907
+ columns: {
1908
+ pageId: import("drizzle-orm/pg-core").PgColumn<{
1909
+ name: "page_id";
1910
+ tableName: "page_labels";
1911
+ dataType: "string";
1912
+ columnType: "PgUUID";
1913
+ data: string;
1914
+ driverParam: string;
1915
+ notNull: true;
1916
+ hasDefault: false;
1917
+ isPrimaryKey: false;
1918
+ isAutoincrement: false;
1919
+ hasRuntimeDefault: false;
1920
+ enumValues: undefined;
1921
+ baseColumn: never;
1922
+ identity: undefined;
1923
+ generated: undefined;
1924
+ }, {}, {}>;
1925
+ labelId: import("drizzle-orm/pg-core").PgColumn<{
1926
+ name: "label_id";
1927
+ tableName: "page_labels";
1928
+ dataType: "string";
1929
+ columnType: "PgUUID";
1930
+ data: string;
1931
+ driverParam: string;
1932
+ notNull: true;
1933
+ hasDefault: false;
1934
+ isPrimaryKey: false;
1935
+ isAutoincrement: false;
1936
+ hasRuntimeDefault: false;
1937
+ enumValues: undefined;
1938
+ baseColumn: never;
1939
+ identity: undefined;
1940
+ generated: undefined;
1941
+ }, {}, {}>;
1942
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
1943
+ name: "workspace_id";
1944
+ tableName: "page_labels";
1945
+ dataType: "string";
1946
+ columnType: "PgUUID";
1947
+ data: string;
1948
+ driverParam: string;
1949
+ notNull: true;
1950
+ hasDefault: false;
1951
+ isPrimaryKey: false;
1952
+ isAutoincrement: false;
1953
+ hasRuntimeDefault: false;
1954
+ enumValues: undefined;
1955
+ baseColumn: never;
1956
+ identity: undefined;
1957
+ generated: undefined;
1958
+ }, {}, {}>;
1959
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
1960
+ name: string;
1961
+ tableName: "page_labels";
1962
+ dataType: "date";
1963
+ columnType: "PgTimestamp";
1964
+ data: Date;
1965
+ driverParam: string;
1966
+ notNull: true;
1967
+ hasDefault: true;
1968
+ isPrimaryKey: false;
1969
+ isAutoincrement: false;
1970
+ hasRuntimeDefault: false;
1971
+ enumValues: undefined;
1972
+ baseColumn: never;
1973
+ identity: undefined;
1974
+ generated: undefined;
1975
+ }, {}, {}>;
1976
+ };
1977
+ dialect: "pg";
1978
+ }>;
1979
+ /**
1980
+ * A page one person put in their sidebar, and where they put it.
1981
+ *
1982
+ * Per user, not per workspace: a favourite is somebody's own shortcut, so the key is the person and
1983
+ * the page rather than anything shared. `workspace_id` is carried anyway — it is what the row-level
1984
+ * policy reads, and without it a favourite would be the one row in this module not fenced by tenant.
1985
+ *
1986
+ * **`position` is `COLLATE "C"` in the migration and has to stay that way.** It is a fractional
1987
+ * index over a base-62 alphabet ordered by code point, so `ORDER BY position` is only the order the
1988
+ * algorithm intended under byte comparison; this database is `en_US.UTF-8`, where `'U' < 'c'` is
1989
+ * false and a list comes back shuffled. drizzle-kit does not carry collation in its snapshot, so if
1990
+ * this migration is ever regenerated, put it back — that is exactly how `properties.position` and
1991
+ * `views.position` lost theirs.
1992
+ */
1993
+ export declare const favorites: import("drizzle-orm/pg-core").PgTableWithColumns<{
1994
+ name: "favorites";
1995
+ schema: "mod_quire";
1996
+ columns: {
1997
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
1998
+ name: "workspace_id";
1999
+ tableName: "favorites";
2000
+ dataType: "string";
2001
+ columnType: "PgUUID";
2002
+ data: string;
2003
+ driverParam: string;
2004
+ notNull: true;
2005
+ hasDefault: false;
2006
+ isPrimaryKey: false;
2007
+ isAutoincrement: false;
2008
+ hasRuntimeDefault: false;
2009
+ enumValues: undefined;
2010
+ baseColumn: never;
2011
+ identity: undefined;
2012
+ generated: undefined;
2013
+ }, {}, {}>;
2014
+ userId: import("drizzle-orm/pg-core").PgColumn<{
2015
+ name: "user_id";
2016
+ tableName: "favorites";
2017
+ dataType: "string";
2018
+ columnType: "PgUUID";
2019
+ data: string;
2020
+ driverParam: string;
2021
+ notNull: true;
2022
+ hasDefault: false;
2023
+ isPrimaryKey: false;
2024
+ isAutoincrement: false;
2025
+ hasRuntimeDefault: false;
2026
+ enumValues: undefined;
2027
+ baseColumn: never;
2028
+ identity: undefined;
2029
+ generated: undefined;
2030
+ }, {}, {}>;
2031
+ pageId: import("drizzle-orm/pg-core").PgColumn<{
2032
+ name: "page_id";
2033
+ tableName: "favorites";
2034
+ dataType: "string";
2035
+ columnType: "PgUUID";
2036
+ data: string;
2037
+ driverParam: string;
2038
+ notNull: true;
2039
+ hasDefault: false;
2040
+ isPrimaryKey: false;
2041
+ isAutoincrement: false;
2042
+ hasRuntimeDefault: false;
2043
+ enumValues: undefined;
2044
+ baseColumn: never;
2045
+ identity: undefined;
2046
+ generated: undefined;
2047
+ }, {}, {}>;
2048
+ position: import("drizzle-orm/pg-core").PgColumn<{
2049
+ name: "position";
2050
+ tableName: "favorites";
2051
+ dataType: "string";
2052
+ columnType: "PgText";
2053
+ data: string;
2054
+ driverParam: string;
2055
+ notNull: true;
2056
+ hasDefault: false;
2057
+ isPrimaryKey: false;
2058
+ isAutoincrement: false;
2059
+ hasRuntimeDefault: false;
2060
+ enumValues: [string, ...string[]];
2061
+ baseColumn: never;
2062
+ identity: undefined;
2063
+ generated: undefined;
2064
+ }, {}, {}>;
2065
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
2066
+ name: string;
2067
+ tableName: "favorites";
2068
+ dataType: "date";
2069
+ columnType: "PgTimestamp";
2070
+ data: Date;
2071
+ driverParam: string;
2072
+ notNull: true;
2073
+ hasDefault: true;
2074
+ isPrimaryKey: false;
2075
+ isAutoincrement: false;
2076
+ hasRuntimeDefault: false;
2077
+ enumValues: undefined;
2078
+ baseColumn: never;
2079
+ identity: undefined;
2080
+ generated: undefined;
2081
+ }, {}, {}>;
2082
+ };
2083
+ dialect: "pg";
2084
+ }>;
2085
+ /**
2086
+ * The last time one person opened one page.
2087
+ *
2088
+ * One row per person per page, updated in place, rather than one row per visit: a log would grow
2089
+ * without bound to answer a question that only ever wants the most recent handful, and it would need
2090
+ * pruning nobody would remember to run. The write is an upsert on the key that bumps `viewed_at`, so
2091
+ * re-opening a page moves it up the list instead of adding to it.
2092
+ */
2093
+ export declare const recentViews: import("drizzle-orm/pg-core").PgTableWithColumns<{
2094
+ name: "recent_views";
2095
+ schema: "mod_quire";
2096
+ columns: {
2097
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
2098
+ name: "workspace_id";
2099
+ tableName: "recent_views";
2100
+ dataType: "string";
2101
+ columnType: "PgUUID";
2102
+ data: string;
2103
+ driverParam: string;
2104
+ notNull: true;
2105
+ hasDefault: false;
2106
+ isPrimaryKey: false;
2107
+ isAutoincrement: false;
2108
+ hasRuntimeDefault: false;
2109
+ enumValues: undefined;
2110
+ baseColumn: never;
2111
+ identity: undefined;
2112
+ generated: undefined;
2113
+ }, {}, {}>;
2114
+ userId: import("drizzle-orm/pg-core").PgColumn<{
2115
+ name: "user_id";
2116
+ tableName: "recent_views";
2117
+ dataType: "string";
2118
+ columnType: "PgUUID";
2119
+ data: string;
2120
+ driverParam: string;
2121
+ notNull: true;
2122
+ hasDefault: false;
2123
+ isPrimaryKey: false;
2124
+ isAutoincrement: false;
2125
+ hasRuntimeDefault: false;
2126
+ enumValues: undefined;
2127
+ baseColumn: never;
2128
+ identity: undefined;
2129
+ generated: undefined;
2130
+ }, {}, {}>;
2131
+ pageId: import("drizzle-orm/pg-core").PgColumn<{
2132
+ name: "page_id";
2133
+ tableName: "recent_views";
2134
+ dataType: "string";
2135
+ columnType: "PgUUID";
2136
+ data: string;
2137
+ driverParam: string;
2138
+ notNull: true;
2139
+ hasDefault: false;
2140
+ isPrimaryKey: false;
2141
+ isAutoincrement: false;
2142
+ hasRuntimeDefault: false;
2143
+ enumValues: undefined;
2144
+ baseColumn: never;
2145
+ identity: undefined;
2146
+ generated: undefined;
2147
+ }, {}, {}>;
2148
+ viewedAt: import("drizzle-orm/pg-core").PgColumn<{
2149
+ name: string;
2150
+ tableName: "recent_views";
2151
+ dataType: "date";
2152
+ columnType: "PgTimestamp";
2153
+ data: Date;
2154
+ driverParam: string;
2155
+ notNull: true;
2156
+ hasDefault: true;
2157
+ isPrimaryKey: false;
2158
+ isAutoincrement: false;
2159
+ hasRuntimeDefault: false;
2160
+ enumValues: undefined;
2161
+ baseColumn: never;
2162
+ identity: undefined;
2163
+ generated: undefined;
2164
+ }, {}, {}>;
2165
+ };
2166
+ dialect: "pg";
2167
+ }>;
2168
+ /**
2169
+ * Somebody who asked to hear about a page.
2170
+ *
2171
+ * Separate from `favorites` because they are different questions: a favourite is "I want this to
2172
+ * hand", a watch is "tell me when this changes". Collapsing them means either a sidebar full of
2173
+ * pages somebody only wanted notifications about, or a notification for every shortcut they made.
2174
+ *
2175
+ * The index leads with the page rather than the user, because the read that matters happens on
2176
+ * every edit — who has to be told about this — while a person's own list of watches is opened rarely.
2177
+ */
2178
+ export declare const watchers: import("drizzle-orm/pg-core").PgTableWithColumns<{
2179
+ name: "watchers";
2180
+ schema: "mod_quire";
2181
+ columns: {
2182
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
2183
+ name: "workspace_id";
2184
+ tableName: "watchers";
2185
+ dataType: "string";
2186
+ columnType: "PgUUID";
2187
+ data: string;
2188
+ driverParam: string;
2189
+ notNull: true;
2190
+ hasDefault: false;
2191
+ isPrimaryKey: false;
2192
+ isAutoincrement: false;
2193
+ hasRuntimeDefault: false;
2194
+ enumValues: undefined;
2195
+ baseColumn: never;
2196
+ identity: undefined;
2197
+ generated: undefined;
2198
+ }, {}, {}>;
2199
+ userId: import("drizzle-orm/pg-core").PgColumn<{
2200
+ name: "user_id";
2201
+ tableName: "watchers";
2202
+ dataType: "string";
2203
+ columnType: "PgUUID";
2204
+ data: string;
2205
+ driverParam: string;
2206
+ notNull: true;
2207
+ hasDefault: false;
2208
+ isPrimaryKey: false;
2209
+ isAutoincrement: false;
2210
+ hasRuntimeDefault: false;
2211
+ enumValues: undefined;
2212
+ baseColumn: never;
2213
+ identity: undefined;
2214
+ generated: undefined;
2215
+ }, {}, {}>;
2216
+ pageId: import("drizzle-orm/pg-core").PgColumn<{
2217
+ name: "page_id";
2218
+ tableName: "watchers";
2219
+ dataType: "string";
2220
+ columnType: "PgUUID";
2221
+ data: string;
2222
+ driverParam: string;
2223
+ notNull: true;
2224
+ hasDefault: false;
2225
+ isPrimaryKey: false;
2226
+ isAutoincrement: false;
2227
+ hasRuntimeDefault: false;
2228
+ enumValues: undefined;
2229
+ baseColumn: never;
2230
+ identity: undefined;
2231
+ generated: undefined;
2232
+ }, {}, {}>;
2233
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
2234
+ name: string;
2235
+ tableName: "watchers";
2236
+ dataType: "date";
2237
+ columnType: "PgTimestamp";
2238
+ data: Date;
2239
+ driverParam: string;
2240
+ notNull: true;
2241
+ hasDefault: true;
2242
+ isPrimaryKey: false;
2243
+ isAutoincrement: false;
2244
+ hasRuntimeDefault: false;
2245
+ enumValues: undefined;
2246
+ baseColumn: never;
2247
+ identity: undefined;
2248
+ generated: undefined;
2249
+ }, {}, {}>;
2250
+ };
2251
+ dialect: "pg";
2252
+ }>;
1772
2253
  /** Every tenant table, so the RLS migration can be checked against one list rather than memory. */
1773
- export declare const TENANT_TABLES: readonly ["spaces", "pages", "page_versions", "comments", "databases", "properties", "views", "relations"];
2254
+ export declare const TENANT_TABLES: readonly ["spaces", "pages", "page_versions", "comments", "databases", "properties", "views", "relations", "labels", "page_labels", "favorites", "recent_views", "watchers"];
1774
2255
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { sql } from 'drizzle-orm';
2
- import { boolean, customType, index, integer, jsonb, pgSchema, text, timestamp, uniqueIndex, uuid, } from 'drizzle-orm/pg-core';
2
+ import { boolean, customType, index, integer, jsonb, pgSchema, primaryKey, text, timestamp, uniqueIndex, uuid, } from 'drizzle-orm/pg-core';
3
3
  /**
4
4
  * This module's tables, in its own Postgres schema.
5
5
  *
@@ -249,6 +249,115 @@ export const relations = schema.table('relations', {
249
249
  index('relations_ws_from_idx').on(t.workspaceId, t.fromPageId),
250
250
  index('relations_ws_to_idx').on(t.workspaceId, t.toPageId),
251
251
  ]);
252
+ /**
253
+ * A word a space puts on pages, so they can be gathered by something other than where they sit.
254
+ *
255
+ * Scoped to a space rather than to the workspace: two teams both wanting "Draft" should not have to
256
+ * agree on what it means, and a label list that spans every space is a list nobody can read.
257
+ *
258
+ * The unique index is on `lower(name)`, not on `name`. Case is not a distinction anybody means here
259
+ * — "Draft" and "draft" next to each other in a picker read as a mistake in the data, and whichever
260
+ * one a person clicks is a coin toss. Lowercasing in the constraint rather than in the column keeps
261
+ * the capitalisation somebody chose while refusing the near-duplicate.
262
+ */
263
+ export const labels = schema.table('labels', {
264
+ id: id(),
265
+ workspaceId: ws(),
266
+ spaceId: uuid('space_id').notNull(),
267
+ name: text('name').notNull(),
268
+ /**
269
+ * One of the closed set in `LabelColour`, stored as text. The contract is what closes it: the
270
+ * palette in `client/database/colours.ts` is the only place a name is turned into a colour pair
271
+ * that has been measured for contrast, and an unknown name falls back to grey rather than
272
+ * rendering a chip with no background.
273
+ */
274
+ colour: text('colour').notNull().default('grey'),
275
+ createdAt: ts('created_at').notNull().defaultNow(),
276
+ }, (t) => [
277
+ uniqueIndex('labels_ws_space_name_uq').on(t.workspaceId, t.spaceId, sql `lower(${t.name})`),
278
+ index('labels_ws_space_idx').on(t.workspaceId, t.spaceId, t.name),
279
+ ]);
280
+ /**
281
+ * A label on a page.
282
+ *
283
+ * The primary key is composite and declared in the table's second argument. Two column-level
284
+ * `.primaryKey()` calls are not a composite key — drizzle emits `PRIMARY KEY` on both columns and
285
+ * Postgres refuses the table with "multiple primary keys for table are not allowed". Because the
286
+ * kernel runs a module's migrations at boot, that is not a broken table but a host service that
287
+ * never binds its port, taking every other module in the process down with it.
288
+ *
289
+ * `(page_id, label_id)` leads with the page because reading a page's labels is what happens on every
290
+ * page load; the second index leads with the label, which is what a "everything tagged X" list asks.
291
+ */
292
+ export const pageLabels = schema.table('page_labels', {
293
+ pageId: uuid('page_id').notNull(),
294
+ labelId: uuid('label_id').notNull(),
295
+ workspaceId: ws(),
296
+ createdAt: ts('created_at').notNull().defaultNow(),
297
+ }, (t) => [
298
+ primaryKey({ columns: [t.pageId, t.labelId] }),
299
+ index('page_labels_ws_label_idx').on(t.workspaceId, t.labelId, t.pageId),
300
+ ]);
301
+ /**
302
+ * A page one person put in their sidebar, and where they put it.
303
+ *
304
+ * Per user, not per workspace: a favourite is somebody's own shortcut, so the key is the person and
305
+ * the page rather than anything shared. `workspace_id` is carried anyway — it is what the row-level
306
+ * policy reads, and without it a favourite would be the one row in this module not fenced by tenant.
307
+ *
308
+ * **`position` is `COLLATE "C"` in the migration and has to stay that way.** It is a fractional
309
+ * index over a base-62 alphabet ordered by code point, so `ORDER BY position` is only the order the
310
+ * algorithm intended under byte comparison; this database is `en_US.UTF-8`, where `'U' < 'c'` is
311
+ * false and a list comes back shuffled. drizzle-kit does not carry collation in its snapshot, so if
312
+ * this migration is ever regenerated, put it back — that is exactly how `properties.position` and
313
+ * `views.position` lost theirs.
314
+ */
315
+ export const favorites = schema.table('favorites', {
316
+ workspaceId: ws(),
317
+ userId: uuid('user_id').notNull(),
318
+ pageId: uuid('page_id').notNull(),
319
+ position: text('position').notNull(),
320
+ createdAt: ts('created_at').notNull().defaultNow(),
321
+ }, (t) => [
322
+ primaryKey({ columns: [t.userId, t.pageId] }),
323
+ index('favorites_ws_user_idx').on(t.workspaceId, t.userId, t.position),
324
+ ]);
325
+ /**
326
+ * The last time one person opened one page.
327
+ *
328
+ * One row per person per page, updated in place, rather than one row per visit: a log would grow
329
+ * without bound to answer a question that only ever wants the most recent handful, and it would need
330
+ * pruning nobody would remember to run. The write is an upsert on the key that bumps `viewed_at`, so
331
+ * re-opening a page moves it up the list instead of adding to it.
332
+ */
333
+ export const recentViews = schema.table('recent_views', {
334
+ workspaceId: ws(),
335
+ userId: uuid('user_id').notNull(),
336
+ pageId: uuid('page_id').notNull(),
337
+ viewedAt: ts('viewed_at').notNull().defaultNow(),
338
+ }, (t) => [
339
+ primaryKey({ columns: [t.userId, t.pageId] }),
340
+ index('recent_views_ws_user_idx').on(t.workspaceId, t.userId, t.viewedAt.desc()),
341
+ ]);
342
+ /**
343
+ * Somebody who asked to hear about a page.
344
+ *
345
+ * Separate from `favorites` because they are different questions: a favourite is "I want this to
346
+ * hand", a watch is "tell me when this changes". Collapsing them means either a sidebar full of
347
+ * pages somebody only wanted notifications about, or a notification for every shortcut they made.
348
+ *
349
+ * The index leads with the page rather than the user, because the read that matters happens on
350
+ * every edit — who has to be told about this — while a person's own list of watches is opened rarely.
351
+ */
352
+ export const watchers = schema.table('watchers', {
353
+ workspaceId: ws(),
354
+ userId: uuid('user_id').notNull(),
355
+ pageId: uuid('page_id').notNull(),
356
+ createdAt: ts('created_at').notNull().defaultNow(),
357
+ }, (t) => [
358
+ primaryKey({ columns: [t.userId, t.pageId] }),
359
+ index('watchers_ws_page_idx').on(t.workspaceId, t.pageId, t.userId),
360
+ ]);
252
361
  /** Every tenant table, so the RLS migration can be checked against one list rather than memory. */
253
362
  export const TENANT_TABLES = [
254
363
  'spaces',
@@ -259,5 +368,10 @@ export const TENANT_TABLES = [
259
368
  'properties',
260
369
  'views',
261
370
  'relations',
371
+ 'labels',
372
+ 'page_labels',
373
+ 'favorites',
374
+ 'recent_views',
375
+ 'watchers',
262
376
  ];
263
377
  //# sourceMappingURL=schema.js.map