@crombolo/data 0.0.0-stage → 0.10.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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +69 -0
  3. package/package.json +31 -4
  4. package/src/bundle-tables/builder.ts +264 -0
  5. package/src/bundle-tables/capability.ts +377 -0
  6. package/src/bundle-tables/chain.ts +105 -0
  7. package/src/bundle-tables/ddl.ts +124 -0
  8. package/src/bundle-tables/guard.ts +116 -0
  9. package/src/bundle-tables/index.ts +7 -0
  10. package/src/bundle-tables/names.ts +58 -0
  11. package/src/bundle-tables/schema.ts +485 -0
  12. package/src/change-report.ts +166 -0
  13. package/src/cluster.ts +108 -0
  14. package/src/database.ts +121 -0
  15. package/src/dialect.ts +136 -0
  16. package/src/index.ts +63 -0
  17. package/src/locks.ts +103 -0
  18. package/src/maintenance.ts +51 -0
  19. package/src/migrations.ts +547 -0
  20. package/src/node-ref.ts +107 -0
  21. package/src/pending.ts +25 -0
  22. package/src/repositories/bundles.ts +223 -0
  23. package/src/repositories/content-types.ts +831 -0
  24. package/src/repositories/data-types.ts +222 -0
  25. package/src/repositories/dictionary.ts +259 -0
  26. package/src/repositories/documents.ts +2389 -0
  27. package/src/repositories/domains.ts +108 -0
  28. package/src/repositories/folders.ts +120 -0
  29. package/src/repositories/form-entries.ts +314 -0
  30. package/src/repositories/form-workflows.ts +213 -0
  31. package/src/repositories/languages.ts +108 -0
  32. package/src/repositories/log-searches.ts +33 -0
  33. package/src/repositories/members.ts +427 -0
  34. package/src/repositories/nodes.ts +361 -0
  35. package/src/repositories/notifications.ts +54 -0
  36. package/src/repositories/public-access.ts +187 -0
  37. package/src/repositories/redirects.ts +274 -0
  38. package/src/repositories/references.ts +162 -0
  39. package/src/repositories/tags.ts +93 -0
  40. package/src/repositories/templates.ts +114 -0
  41. package/src/repositories/transfer-runs.ts +208 -0
  42. package/src/repositories/user-data.ts +90 -0
  43. package/src/repositories/users.ts +898 -0
  44. package/src/schema/assistant.ts +74 -0
  45. package/src/schema/built-in-content-types.ts +106 -0
  46. package/src/schema/built-in-media-types.ts +335 -0
  47. package/src/schema/built-in-member-types.ts +61 -0
  48. package/src/schema/bundle-components.ts +49 -0
  49. package/src/schema/bundle-package.ts +27 -0
  50. package/src/schema/bundles-rename.ts +32 -0
  51. package/src/schema/change-report.ts +46 -0
  52. package/src/schema/client-id-prefix.ts +43 -0
  53. package/src/schema/content-editing.ts +50 -0
  54. package/src/schema/content-seed.ts +586 -0
  55. package/src/schema/content.ts +263 -0
  56. package/src/schema/created-packages.ts +56 -0
  57. package/src/schema/elements.ts +62 -0
  58. package/src/schema/form-permission-prefix.ts +50 -0
  59. package/src/schema/form-permissions.ts +26 -0
  60. package/src/schema/form-workflows.ts +54 -0
  61. package/src/schema/forms.ts +63 -0
  62. package/src/schema/group-permissions.ts +20 -0
  63. package/src/schema/identity.ts +176 -0
  64. package/src/schema/log-viewer.ts +68 -0
  65. package/src/schema/member-property-type.ts +30 -0
  66. package/src/schema/members.ts +97 -0
  67. package/src/schema/plan.ts +77 -0
  68. package/src/schema/public-access.ts +56 -0
  69. package/src/schema/recycle-bin.ts +21 -0
  70. package/src/schema/redirects.ts +57 -0
  71. package/src/schema/rich-text-ui.ts +34 -0
  72. package/src/schema/seed.ts +298 -0
  73. package/src/schema/server-role.ts +21 -0
  74. package/src/schema/since-version.ts +26 -0
  75. package/src/schema/transfer-run-package.ts +27 -0
  76. package/src/schema/transfer-runs.ts +71 -0
  77. package/src/schema/upgrade-report.ts +43 -0
  78. package/src/schema/users-localisation.ts +85 -0
  79. package/src/schema/values.ts +108 -0
  80. package/src/schema-ops.ts +35 -0
  81. package/src/schema-state.ts +196 -0
  82. package/README.md +0 -3
@@ -0,0 +1,208 @@
1
+ /**
2
+ * What content imports have been applied here, and what each one touched.
3
+ *
4
+ * The rows exist so that reverting is a lookup rather than a guess: each change
5
+ * remembers the event its node was at beforehand and the published event it was
6
+ * serving, which together *are* the previously-live state.
7
+ */
8
+ import type { Db } from '../database.ts'
9
+ import { DbDate } from '../dialect.ts'
10
+
11
+ export type TransferDirection = 'import' | 'revert'
12
+ export type TransferRunStatus = 'applied' | 'reverted' | 'failed'
13
+ /**
14
+ * What a run did to one node.
15
+ *
16
+ * `unchanged` is recorded too: a run that decided to do nothing still owns the
17
+ * node. `recycle` is what a *revert* does to a node the import created, and it
18
+ * is recorded so that reverting the revert knows to bring it back out of the
19
+ * bin rather than only restoring its values.
20
+ */
21
+ export type TransferAction = 'create' | 'update' | 'unchanged' | 'skip' | 'recycle'
22
+
23
+ export interface TransferRun {
24
+ id: string
25
+ bundleId: string
26
+ bundleLabel: string | null
27
+ /** The npm package the content arrived in, and its version. */
28
+ packageName: string | null
29
+ packageVersion: string | null
30
+ direction: TransferDirection
31
+ revertsRunId: string | null
32
+ schemaStateId: number
33
+ startedAt: Date
34
+ finishedAt: Date | null
35
+ appliedBy: string | null
36
+ nodeCount: number
37
+ status: TransferRunStatus
38
+ }
39
+
40
+ export interface TransferChange {
41
+ id: number
42
+ runId: string
43
+ nodeKey: string
44
+ kind: string
45
+ action: TransferAction
46
+ /** The node's latest event before this run, or null when the run created it. */
47
+ beforeEventId: number | null
48
+ /** The event it was serving before this run, or null if it was not published. */
49
+ beforePublishedEventId: number | null
50
+ /** The event this run appended, when it appended one. */
51
+ eventId: number | null
52
+ }
53
+
54
+ export class TransferRunRepository {
55
+ #db: Db
56
+ constructor(db: Db) {
57
+ this.#db = db
58
+ }
59
+
60
+ async start(run: Omit<TransferRun, 'finishedAt' | 'status'>): Promise<void> {
61
+ await this.#db.exec(
62
+ `INSERT INTO content_transfer_run
63
+ (id, bundle_id, bundle_label, package_name, package_version, direction, reverts_run_id, schema_state_id, started_at, applied_by, node_count, status)
64
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'applied')`,
65
+ [
66
+ run.id,
67
+ run.bundleId,
68
+ run.bundleLabel,
69
+ run.packageName,
70
+ run.packageVersion,
71
+ run.direction,
72
+ run.revertsRunId,
73
+ run.schemaStateId,
74
+ DbDate.toDb(run.startedAt),
75
+ run.appliedBy,
76
+ run.nodeCount,
77
+ ],
78
+ )
79
+ }
80
+
81
+ async finish(id: string, nodeCount: number): Promise<void> {
82
+ await this.#db.exec(
83
+ 'UPDATE content_transfer_run SET finished_at = ?, node_count = ? WHERE id = ?',
84
+ [DbDate.toDb(new Date()), nodeCount, id],
85
+ )
86
+ }
87
+
88
+ async setStatus(id: string, status: TransferRunStatus): Promise<void> {
89
+ await this.#db.exec('UPDATE content_transfer_run SET status = ? WHERE id = ?', [status, id])
90
+ }
91
+
92
+ async record(change: Omit<TransferChange, 'id'>): Promise<void> {
93
+ await this.#db.exec(
94
+ `INSERT INTO content_transfer_change
95
+ (run_id, node_key, kind, action, before_event_id, before_published_event_id, event_id)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
97
+ [
98
+ change.runId,
99
+ change.nodeKey,
100
+ change.kind,
101
+ change.action,
102
+ change.beforeEventId,
103
+ change.beforePublishedEventId,
104
+ change.eventId,
105
+ ],
106
+ )
107
+ }
108
+
109
+ async byId(id: string): Promise<TransferRun | undefined> {
110
+ const rows = await this.#db.query('SELECT * FROM content_transfer_run WHERE id = ?', [id])
111
+ return rows[0] ? mapRun(rows[0]) : undefined
112
+ }
113
+
114
+ /**
115
+ * The import of this bundle that still stands here, if there is one. A run
116
+ * that was reverted does not count: the content is gone, so importing again
117
+ * is the right answer. This is what makes `start --bundle` import once.
118
+ */
119
+ async appliedFor(bundleId: string): Promise<TransferRun | undefined> {
120
+ const rows = await this.#db.query(
121
+ `SELECT * FROM content_transfer_run
122
+ WHERE bundle_id = ? AND direction = 'import' AND status = 'applied'
123
+ ORDER BY started_at DESC, id DESC LIMIT 1`,
124
+ [bundleId],
125
+ )
126
+ return rows[0] ? mapRun(rows[0]) : undefined
127
+ }
128
+
129
+ /** The latest standing import of a package's content, whichever version brought it. */
130
+ async appliedForPackage(packageName: string): Promise<TransferRun | undefined> {
131
+ const rows = await this.#db.query(
132
+ `SELECT * FROM content_transfer_run
133
+ WHERE package_name = ? AND direction = 'import' AND status = 'applied'
134
+ ORDER BY started_at DESC, id DESC LIMIT 1`,
135
+ [packageName],
136
+ )
137
+ return rows[0] ? mapRun(rows[0]) : undefined
138
+ }
139
+
140
+ /** Runs, newest first. */
141
+ async recent(limit = 20): Promise<TransferRun[]> {
142
+ const rows = await this.#db.query(
143
+ 'SELECT * FROM content_transfer_run ORDER BY started_at DESC, id DESC LIMIT ?',
144
+ [limit],
145
+ )
146
+ return rows.map(mapRun)
147
+ }
148
+
149
+ async changes(runId: string): Promise<TransferChange[]> {
150
+ const rows = await this.#db.query(
151
+ 'SELECT * FROM content_transfer_change WHERE run_id = ? ORDER BY id',
152
+ [runId],
153
+ )
154
+ return rows.map(mapChange)
155
+ }
156
+
157
+ /**
158
+ * Runs after this one that touched any of the same nodes. Reverting while one
159
+ * of these stands would quietly undo its work too, so revert refuses.
160
+ */
161
+ async laterRunsTouching(runId: string): Promise<string[]> {
162
+ const rows = await this.#db.query<{ id: string }>(
163
+ `SELECT DISTINCT later.id
164
+ FROM content_transfer_change mine
165
+ JOIN content_transfer_change theirs ON theirs.node_key = mine.node_key
166
+ JOIN content_transfer_run later ON later.id = theirs.run_id
167
+ JOIN content_transfer_run self ON self.id = mine.run_id
168
+ WHERE mine.run_id = ?
169
+ AND theirs.run_id <> mine.run_id
170
+ AND later.started_at >= self.started_at
171
+ AND later.status = 'applied'`,
172
+ [runId],
173
+ )
174
+ return rows.map((row) => String(row.id))
175
+ }
176
+ }
177
+
178
+ function mapRun(row: Record<string, unknown>): TransferRun {
179
+ return {
180
+ id: String(row.id),
181
+ bundleId: String(row.bundle_id),
182
+ bundleLabel: (row.bundle_label as string | null) ?? null,
183
+ packageName: (row.package_name as string | null) ?? null,
184
+ packageVersion: (row.package_version as string | null) ?? null,
185
+ direction: String(row.direction) as TransferDirection,
186
+ revertsRunId: (row.reverts_run_id as string | null) ?? null,
187
+ schemaStateId: Number(row.schema_state_id),
188
+ startedAt: DbDate.fromDb(row.started_at) ?? new Date(0),
189
+ finishedAt: DbDate.fromDb(row.finished_at) ?? null,
190
+ appliedBy: (row.applied_by as string | null) ?? null,
191
+ nodeCount: Number(row.node_count),
192
+ status: String(row.status) as TransferRunStatus,
193
+ }
194
+ }
195
+
196
+ function mapChange(row: Record<string, unknown>): TransferChange {
197
+ const int = (value: unknown) => (value === null || value === undefined ? null : Number(value))
198
+ return {
199
+ id: Number(row.id),
200
+ runId: String(row.run_id),
201
+ nodeKey: String(row.node_key),
202
+ kind: String(row.kind),
203
+ action: String(row.action) as TransferAction,
204
+ beforeEventId: int(row.before_event_id),
205
+ beforePublishedEventId: int(row.before_published_event_id),
206
+ eventId: int(row.event_id),
207
+ }
208
+ }
@@ -0,0 +1,90 @@
1
+ /** Per-user key/value data the backoffice keeps between sessions (Umbraco's `umbracoUserData`). */
2
+ import { normaliseUuid } from '@crombolo/core'
3
+ import type { Db } from '../database.ts'
4
+
5
+ export interface UserDataEntry {
6
+ key: string
7
+ group: string
8
+ identifier: string
9
+ value: string
10
+ }
11
+
12
+ export type UserDataResult = 'Success' | 'NotFound' | 'AlreadyExists'
13
+
14
+ const map = (row: Record<string, unknown>): UserDataEntry => ({
15
+ key: normaliseUuid(String(row.key)),
16
+ group: String(row.data_group),
17
+ identifier: String(row.identifier),
18
+ value: String(row.value),
19
+ })
20
+
21
+ export class UserDataRepository {
22
+ #db: Db
23
+
24
+ constructor(db: Db) {
25
+ this.#db = db
26
+ }
27
+
28
+ async list(
29
+ userId: number,
30
+ filter: { groups?: readonly string[]; identifiers?: readonly string[] },
31
+ skip: number,
32
+ take: number,
33
+ ): Promise<{ total: number; items: UserDataEntry[] }> {
34
+ const where = ['user_id = ?']
35
+ const params: unknown[] = [userId]
36
+ if (filter.groups?.length) {
37
+ where.push(`data_group IN (${filter.groups.map(() => '?').join(', ')})`)
38
+ params.push(...filter.groups)
39
+ }
40
+ if (filter.identifiers?.length) {
41
+ where.push(`identifier IN (${filter.identifiers.map(() => '?').join(', ')})`)
42
+ params.push(...filter.identifiers)
43
+ }
44
+ const rows = await this.#db.query(
45
+ `SELECT key, data_group, identifier, value FROM user_data WHERE ${where.join(' AND ')}
46
+ ORDER BY data_group, identifier`,
47
+ params,
48
+ )
49
+ return { total: rows.length, items: rows.slice(skip, skip + take).map(map) }
50
+ }
51
+
52
+ async get(userId: number, key: string): Promise<UserDataEntry | undefined> {
53
+ const rows = await this.#db.query(
54
+ 'SELECT key, data_group, identifier, value FROM user_data WHERE user_id = ? AND key = ?',
55
+ [userId, normaliseUuid(key)],
56
+ )
57
+ return rows[0] ? map(rows[0]) : undefined
58
+ }
59
+
60
+ async create(userId: number, entry: UserDataEntry): Promise<UserDataResult> {
61
+ const clash = await this.#db.query(
62
+ 'SELECT 1 FROM user_data WHERE key = ? OR (user_id = ? AND data_group = ? AND identifier = ?)',
63
+ [normaliseUuid(entry.key), userId, entry.group, entry.identifier],
64
+ )
65
+ if (clash.length > 0) return 'AlreadyExists'
66
+ await this.#db.exec(
67
+ 'INSERT INTO user_data (key, user_id, data_group, identifier, value) VALUES (?, ?, ?, ?, ?)',
68
+ [normaliseUuid(entry.key), userId, entry.group, entry.identifier, entry.value],
69
+ )
70
+ return 'Success'
71
+ }
72
+
73
+ async update(userId: number, entry: UserDataEntry): Promise<UserDataResult> {
74
+ if (!(await this.get(userId, entry.key))) return 'NotFound'
75
+ await this.#db.exec(
76
+ 'UPDATE user_data SET data_group = ?, identifier = ?, value = ? WHERE user_id = ? AND key = ?',
77
+ [entry.group, entry.identifier, entry.value, userId, normaliseUuid(entry.key)],
78
+ )
79
+ return 'Success'
80
+ }
81
+
82
+ async delete(userId: number, key: string): Promise<UserDataResult> {
83
+ if (!(await this.get(userId, key))) return 'NotFound'
84
+ await this.#db.exec('DELETE FROM user_data WHERE user_id = ? AND key = ?', [
85
+ userId,
86
+ normaliseUuid(key),
87
+ ])
88
+ return 'Success'
89
+ }
90
+ }