@beechcms/api 0.6.0-preview.1 → 0.6.0-preview.2
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/assets/dashboard/BeechLogo.svg +18 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/chart-BrJFdsGZ.js +39 -0
- package/assets/dashboard/assets/index-CIaoMxuB.css +1 -0
- package/assets/dashboard/assets/index-wTKgygsg.js +636 -0
- package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +1 -0
- package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +1 -0
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/noResult.svg +42 -42
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/migrations/0000_v040_base.sql +265 -265
- package/migrations/0029_automations.sql +14 -14
- package/migrations/0030_test_seeds.sql +125 -125
- package/package.json +2 -2
- package/src/auth/__fixtures__/in-memory-hash-provider.ts +17 -17
- package/src/auth/__fixtures__/static-token-service.ts +22 -22
- package/src/auth/bcrypt-hash-provider.ts +24 -24
- package/src/auth/constants.ts +14 -14
- package/src/auth/generate-refresh-token.test.ts +23 -23
- package/src/auth/hash-provider.test.ts +55 -55
- package/src/auth/jose-token-service.ts +62 -62
- package/src/auth/jwt-claims-passthrough.test.ts +87 -87
- package/src/auth/login.test.ts +100 -100
- package/src/auth/login.ts +78 -78
- package/src/auth/refresh.ts +11 -11
- package/src/auth/token-service.test.ts +90 -90
- package/src/factory.ts +370 -368
- package/src/features/automations/__tests__/action-executors.test.ts +255 -255
- package/src/features/automations/__tests__/automation-runner.test.ts +196 -196
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +60 -60
- package/src/features/automations/__tests__/automations.handler.test.ts +264 -264
- package/src/features/automations/__tests__/automations.repository.test.ts +163 -163
- package/src/features/automations/__tests__/automations.schema.test.ts +224 -224
- package/src/features/automations/__tests__/context-resolver.test.ts +126 -126
- package/src/features/automations/__tests__/cron-runner.test.ts +267 -267
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +144 -144
- package/src/features/automations/__tests__/set-variable.executor.test.ts +383 -383
- package/src/features/automations/__tests__/template-grammar.test.ts +308 -308
- package/src/features/automations/__tests__/webhook.executor.test.ts +142 -142
- package/src/features/automations/__tests__/when-evaluator.test.ts +274 -274
- package/src/features/automations/__tests__/when-pushdown.test.ts +281 -281
- package/src/features/automations/action-executors/create-entry.executor.ts +27 -27
- package/src/features/automations/action-executors/edit-field.executor.ts +26 -26
- package/src/features/automations/action-executors/index.ts +37 -37
- package/src/features/automations/action-executors/send-mail.executor.ts +53 -53
- package/src/features/automations/action-executors/set-variable.executor.ts +159 -159
- package/src/features/automations/action-executors/webhook.executor.ts +67 -67
- package/src/features/automations/automation-runner.ts +85 -85
- package/src/features/automations/automation-runner.utils.ts +47 -47
- package/src/features/automations/automations.handler.ts +197 -197
- package/src/features/automations/automations.schema.ts +177 -177
- package/src/features/automations/context-resolver.ts +152 -152
- package/src/features/automations/cron-runner.ts +140 -140
- package/src/features/automations/cron-runner.utils.ts +44 -44
- package/src/features/automations/filter-translation.ts +46 -46
- package/src/features/automations/index.ts +16 -16
- package/src/features/automations/template-grammar.ts +245 -245
- package/src/features/automations/var-access-resolver.ts +140 -140
- package/src/features/automations/when-evaluator.ts +87 -87
- package/src/features/automations/when-pushdown.ts +57 -57
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -359
- package/src/features/backrefs/backrefs.handler.ts +168 -168
- package/src/features/backrefs/d1-backref.repository.ts +84 -84
- package/src/features/backrefs/index.ts +5 -5
- package/src/features/content/constants.ts +16 -16
- package/src/features/content/handlers/bulk.handler.ts +185 -185
- package/src/features/content/handlers/create.ts +129 -129
- package/src/features/content/handlers/delete.ts +62 -62
- package/src/features/content/handlers/facets.ts +49 -49
- package/src/features/content/handlers/get.ts +120 -120
- package/src/features/content/handlers/helpers.test.ts +180 -180
- package/src/features/content/handlers/helpers.ts +93 -93
- package/src/features/content/handlers/list.ts +174 -174
- package/src/features/content/handlers/update.ts +174 -174
- package/src/features/content/index.ts +26 -26
- package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +388 -0
- package/src/features/dashboard-layout/dashboard-layout.handler.ts +206 -0
- package/src/features/dashboard-layout/index.ts +5 -0
- package/src/features/draft/draft.handler.ts +195 -195
- package/src/features/draft/draft.middleware.ts +66 -66
- package/src/features/draft/index.ts +5 -5
- package/src/features/email/email.provider.ts +42 -42
- package/src/features/email/email.service.ts +98 -98
- package/src/features/email/email.types.ts +119 -119
- package/src/features/email/index.ts +33 -33
- package/src/features/email/providers/resend.ts +67 -67
- package/src/features/email/providers/smtp.ts +55 -55
- package/src/features/email/templates/automation-mail.ts +19 -19
- package/src/features/email/templates/password-changed.ts +63 -63
- package/src/features/email/templates/password-reset.ts +68 -68
- package/src/features/email/templates/shell.ts +96 -96
- package/src/features/notifications/index.ts +5 -5
- package/src/features/notifications/notifications.handler.ts +105 -105
- package/src/features/password-reset/index.ts +19 -19
- package/src/features/password-reset/request.ts +92 -92
- package/src/features/password-reset/reset.ts +102 -102
- package/src/features/rotate-field/index.ts +5 -5
- package/src/features/rotate-field/rotate-field.handler.ts +132 -132
- package/src/features/rotate-field/rotate-field.schema.ts +17 -17
- package/src/features/schema/schema.handler.ts +131 -131
- package/src/features/seeds/index.ts +5 -5
- package/src/features/seeds/seeds.handler.test.ts +1199 -632
- package/src/features/seeds/seeds.handler.ts +558 -693
- package/src/features/settings/__tests__/settings.handler.test.ts +555 -555
- package/src/features/settings/settings.handler.ts +392 -392
- package/src/features/setup/index.ts +288 -204
- package/src/features/stats/index.ts +5 -5
- package/src/features/stats/stats.handler.ts +420 -420
- package/src/features/webhooks/index.ts +63 -63
- package/src/index.ts +65 -65
- package/src/media-utils.ts +82 -82
- package/src/middleware/auth-providers.middleware.test.ts +85 -0
- package/src/middleware/auth-providers.middleware.ts +45 -45
- package/src/middleware/observability.middleware.test.ts +93 -0
- package/src/middleware/observability.middleware.ts +70 -70
- package/src/middleware/rate-limit.middleware.ts +45 -45
- package/src/middleware/repository.middleware.ts +117 -114
- package/src/middleware/seed-registry.middleware.test.ts +97 -97
- package/src/middleware/seed-registry.middleware.ts +21 -21
- package/src/middleware/storage.middleware.ts +25 -25
- package/src/middleware.ts +45 -45
- package/src/public/access-policy.ts +27 -27
- package/src/public/api-key-middleware.ts +57 -57
- package/src/public/cache-utils.ts +38 -38
- package/src/public/entry-projection.ts +46 -46
- package/src/public/idempotency.ts +23 -23
- package/src/public/index.ts +16 -16
- package/src/public/problem-details.ts +124 -124
- package/src/public/public-add.ts +114 -114
- package/src/public/public-edit.ts +163 -163
- package/src/public/public-errors.ts +19 -19
- package/src/public/public-read.ts +63 -63
- package/src/public/public-routes.ts +88 -88
- package/src/public/query-builder.test.ts +224 -224
- package/src/public/query-builder.ts +156 -156
- package/src/public/rate-limit-middleware.ts +34 -34
- package/src/public/read-list.ts +54 -54
- package/src/public/read-single.ts +48 -48
- package/src/public/response-builder.ts +30 -30
- package/src/public/sanitize.ts +69 -69
- package/src/public/slug-utils.ts +18 -18
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +30 -30
- package/src/rate-limit/cloudflare-rate-limiter.ts +15 -15
- package/src/rate-limit/in-memory-rate-limiter.test.ts +37 -37
- package/src/rate-limit/in-memory-rate-limiter.ts +17 -17
- package/src/rate-limit/no-op-rate-limiter.test.ts +22 -22
- package/src/rate-limit/no-op-rate-limiter.ts +11 -11
- package/src/search-utils.test.ts +211 -211
- package/src/search-utils.ts +213 -213
- package/src/search.ts +65 -65
- package/src/shared/apply-policies.test.ts +81 -81
- package/src/shared/apply-policies.ts +67 -67
- package/src/shared/automations.repository.d1.ts +150 -150
- package/src/shared/background-notification-service.test.ts +62 -62
- package/src/shared/background-notification-service.ts +52 -52
- package/src/shared/base.repository.d1.ts +32 -32
- package/src/shared/content-utils.test.ts +165 -165
- package/src/shared/content-utils.ts +86 -86
- package/src/shared/content.repository.d1.test.ts +507 -458
- package/src/shared/content.repository.d1.ts +851 -851
- package/src/shared/d1-activity-log.repository.test.ts +140 -140
- package/src/shared/d1-activity-log.repository.ts +105 -105
- package/src/shared/d1-activity-logger.test.ts +86 -86
- package/src/shared/d1-activity-logger.ts +67 -67
- package/src/shared/d1-analytics.repository.test.ts +78 -78
- package/src/shared/d1-analytics.repository.ts +85 -85
- package/src/shared/d1-content-scan.repository.test.ts +80 -80
- package/src/shared/d1-content-scan.repository.ts +37 -37
- package/src/shared/d1-notification.repository.test.ts +128 -128
- package/src/shared/d1-notification.repository.ts +118 -118
- package/src/shared/d1-password-reset-token.repository.test.ts +81 -81
- package/src/shared/d1-password-reset-token.repository.ts +56 -56
- package/src/shared/d1-search.repository.test.ts +87 -87
- package/src/shared/d1-search.repository.ts +88 -88
- package/src/shared/d1-session.repository.test.ts +125 -125
- package/src/shared/d1-session.repository.ts +102 -102
- package/src/shared/d1-setup-checklist.repository.ts +36 -36
- package/src/shared/d1-user.repository.test.ts +151 -151
- package/src/shared/d1-user.repository.ts +119 -119
- package/src/shared/d1-widget.repository.test.ts +267 -221
- package/src/shared/d1-widget.repository.ts +385 -341
- package/src/shared/dashboard-layout.repository.d1.test.ts +135 -0
- package/src/shared/dashboard-layout.repository.d1.ts +58 -0
- package/src/shared/demo-data-sql.ts +216 -54
- package/src/shared/demo-data.repository.d1.ts +20 -20
- package/src/shared/execution-context-scheduler.ts +13 -13
- package/src/shared/fixed-clock.ts +25 -25
- package/src/shared/fts-sync.ts +8 -8
- package/src/shared/idempotency.repository.d1.test.ts +83 -83
- package/src/shared/idempotency.repository.d1.ts +60 -60
- package/src/shared/in-memory-activity-logger.ts +19 -19
- package/src/shared/in-memory-notification-service.ts +19 -19
- package/src/shared/in-memory-seed.repository.ts +58 -51
- package/src/shared/media.repository.d1.test.ts +107 -107
- package/src/shared/media.repository.d1.ts +68 -68
- package/src/shared/qstash-notification-service.test.ts +67 -67
- package/src/shared/qstash-notification-service.ts +55 -55
- package/src/shared/query-utils.ts +141 -141
- package/src/shared/request-utils.ts +26 -26
- package/src/shared/schema-mutator.d1.test.ts +142 -0
- package/src/shared/schema-mutator.d1.ts +64 -64
- package/src/shared/seed-layout.repository.d1.test.ts +114 -114
- package/src/shared/seed-layout.repository.d1.ts +62 -62
- package/src/shared/seed-registry-cache.test.ts +68 -68
- package/src/shared/seed-registry-cache.ts +49 -49
- package/src/shared/seed.repository.d1.test.ts +143 -143
- package/src/shared/seed.repository.d1.ts +98 -98
- package/src/shared/sequential-id-generator.ts +33 -33
- package/src/shared/site-settings.repository.d1.ts +53 -53
- package/src/shared/storage/factory.ts +41 -41
- package/src/shared/storage/s3-bucket.ts +195 -195
- package/src/shared/storage-utils.ts +40 -40
- package/src/shared/system-stats.repository.d1.test.ts +58 -58
- package/src/shared/system-stats.repository.d1.ts +48 -48
- package/src/types.ts +90 -87
- package/src/upload.ts +198 -198
- package/src/widget.test.ts +85 -0
- package/src/widget.ts +251 -212
- package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
- package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
-
// See LICENSE in the repository root for license terms.
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
6
|
-
import { D1AutomationRepository } from '../../../shared/automations.repository.d1'
|
|
7
|
-
|
|
8
|
-
function makeDb() {
|
|
9
|
-
const calls: { sql: string; bindings: unknown[] }[] = []
|
|
10
|
-
let nextAllResult: unknown[] = []
|
|
11
|
-
let nextFirstResult: unknown = null
|
|
12
|
-
|
|
13
|
-
const db = {
|
|
14
|
-
_calls: calls,
|
|
15
|
-
_setAllResult(rows: unknown[]) { nextAllResult = rows },
|
|
16
|
-
_setFirstResult(row: unknown) { nextFirstResult = row },
|
|
17
|
-
prepare(sql: string) {
|
|
18
|
-
let boundValues: unknown[] = []
|
|
19
|
-
const stmt = {
|
|
20
|
-
bind(...args: unknown[]) { boundValues = args; return stmt },
|
|
21
|
-
async run() {
|
|
22
|
-
calls.push({ sql, bindings: boundValues })
|
|
23
|
-
return { success: true }
|
|
24
|
-
},
|
|
25
|
-
async all() {
|
|
26
|
-
calls.push({ sql, bindings: boundValues })
|
|
27
|
-
return { results: nextAllResult }
|
|
28
|
-
},
|
|
29
|
-
async first() {
|
|
30
|
-
calls.push({ sql, bindings: boundValues })
|
|
31
|
-
return nextFirstResult
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
return stmt
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
return db as unknown as D1Database & typeof db
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const fixtureRow = {
|
|
41
|
-
id: 'abc',
|
|
42
|
-
seed_slug: 'posts',
|
|
43
|
-
name: 'test',
|
|
44
|
-
enabled: 1,
|
|
45
|
-
triggers: JSON.stringify([{ event: 'create' }]),
|
|
46
|
-
trigger_conditions: null,
|
|
47
|
-
actions: JSON.stringify([{ type: 'webhook', url: 'https://example.com' }]),
|
|
48
|
-
created_at: 1000,
|
|
49
|
-
updated_at: 1000,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
describe('D1AutomationRepository.create', () => {
|
|
53
|
-
it('serialises actions and trigger_conditions to JSON', async () => {
|
|
54
|
-
const db = makeDb()
|
|
55
|
-
const repo = new D1AutomationRepository(db)
|
|
56
|
-
const whenNode = {
|
|
57
|
-
kind: 'group' as const,
|
|
58
|
-
op: 'AND' as const,
|
|
59
|
-
children: [{ kind: 'predicate' as const, left: { kind: 'ref' as const, key: 'this.status' }, op: 'eq' as const, right: { kind: 'literal' as const, value: 'published' } }],
|
|
60
|
-
}
|
|
61
|
-
await repo.create({
|
|
62
|
-
seed_slug: 'posts',
|
|
63
|
-
name: 'test',
|
|
64
|
-
triggers: [{ event: 'create' }],
|
|
65
|
-
trigger_conditions: whenNode,
|
|
66
|
-
actions: [{ type: 'webhook', url: 'https://example.com' }],
|
|
67
|
-
})
|
|
68
|
-
expect(db._calls).toHaveLength(1)
|
|
69
|
-
const { bindings } = db._calls[0]
|
|
70
|
-
// triggers is index 3, trigger_conditions is index 4, actions is index 5
|
|
71
|
-
expect(typeof bindings[3]).toBe('string')
|
|
72
|
-
expect(JSON.parse(bindings[3] as string)).toEqual([{ event: 'create' }])
|
|
73
|
-
expect(typeof bindings[4]).toBe('string')
|
|
74
|
-
expect(JSON.parse(bindings[4] as string)).toEqual(whenNode)
|
|
75
|
-
expect(typeof bindings[5]).toBe('string')
|
|
76
|
-
expect(JSON.parse(bindings[5] as string)).toEqual([{ type: 'webhook', url: 'https://example.com' }])
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('stores null for missing trigger_conditions', async () => {
|
|
80
|
-
const db = makeDb()
|
|
81
|
-
const repo = new D1AutomationRepository(db)
|
|
82
|
-
await repo.create({
|
|
83
|
-
seed_slug: 'posts',
|
|
84
|
-
name: 'test',
|
|
85
|
-
triggers: [{ event: 'create' }],
|
|
86
|
-
trigger_conditions: null,
|
|
87
|
-
actions: [{ type: 'webhook', url: 'https://example.com' }],
|
|
88
|
-
})
|
|
89
|
-
const { bindings } = db._calls[0]
|
|
90
|
-
expect(bindings[4]).toBeNull()
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
describe('D1AutomationRepository.update', () => {
|
|
95
|
-
it('builds SET clause only from provided keys', async () => {
|
|
96
|
-
const db = makeDb()
|
|
97
|
-
const repo = new D1AutomationRepository(db)
|
|
98
|
-
await repo.update('abc', { name: 'renamed' })
|
|
99
|
-
expect(db._calls).toHaveLength(1)
|
|
100
|
-
expect(db._calls[0].sql).toContain('name = ?')
|
|
101
|
-
expect(db._calls[0].sql).not.toContain('seed_slug')
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('short-circuits when no fields provided', async () => {
|
|
105
|
-
const db = makeDb()
|
|
106
|
-
const repo = new D1AutomationRepository(db)
|
|
107
|
-
await repo.update('abc', {})
|
|
108
|
-
expect(db._calls).toHaveLength(0)
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
describe('D1AutomationRepository.toggle', () => {
|
|
113
|
-
it('only writes enabled and updated_at', async () => {
|
|
114
|
-
const db = makeDb()
|
|
115
|
-
const repo = new D1AutomationRepository(db)
|
|
116
|
-
await repo.toggle('abc', false)
|
|
117
|
-
expect(db._calls).toHaveLength(1)
|
|
118
|
-
const { sql, bindings } = db._calls[0]
|
|
119
|
-
expect(sql).toBe('UPDATE automations SET enabled = ?, updated_at = ? WHERE id = ?')
|
|
120
|
-
expect(bindings[0]).toBe(0)
|
|
121
|
-
expect(bindings[2]).toBe('abc')
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
describe('rowToAutomation JSON round-trip', () => {
|
|
126
|
-
it('deserialises trigger_conditions as null when column is null', async () => {
|
|
127
|
-
const db = makeDb()
|
|
128
|
-
db._setAllResult([fixtureRow])
|
|
129
|
-
const repo = new D1AutomationRepository(db)
|
|
130
|
-
const rows = await repo.list('posts')
|
|
131
|
-
expect(rows[0].trigger_conditions).toBeNull()
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('deserialises trigger_conditions as WhenNode when column contains WhenNode JSON', async () => {
|
|
135
|
-
const db = makeDb()
|
|
136
|
-
const whenNode = {
|
|
137
|
-
kind: 'group',
|
|
138
|
-
op: 'AND',
|
|
139
|
-
children: [{
|
|
140
|
-
kind: 'predicate',
|
|
141
|
-
left: { kind: 'ref', key: 'this.status' },
|
|
142
|
-
op: 'eq',
|
|
143
|
-
right: { kind: 'literal', value: 'published' },
|
|
144
|
-
}],
|
|
145
|
-
}
|
|
146
|
-
const rowWithConditions = {
|
|
147
|
-
...fixtureRow,
|
|
148
|
-
trigger_conditions: JSON.stringify(whenNode),
|
|
149
|
-
}
|
|
150
|
-
db._setAllResult([rowWithConditions])
|
|
151
|
-
const repo = new D1AutomationRepository(db)
|
|
152
|
-
const rows = await repo.list('posts')
|
|
153
|
-
expect(rows[0].trigger_conditions).toEqual(whenNode)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('deserialises actions as parsed array', async () => {
|
|
157
|
-
const db = makeDb()
|
|
158
|
-
db._setAllResult([fixtureRow])
|
|
159
|
-
const repo = new D1AutomationRepository(db)
|
|
160
|
-
const rows = await repo.list('posts')
|
|
161
|
-
expect(rows[0].actions).toEqual([{ type: 'webhook', url: 'https://example.com' }])
|
|
162
|
-
})
|
|
163
|
-
})
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
6
|
+
import { D1AutomationRepository } from '../../../shared/automations.repository.d1'
|
|
7
|
+
|
|
8
|
+
function makeDb() {
|
|
9
|
+
const calls: { sql: string; bindings: unknown[] }[] = []
|
|
10
|
+
let nextAllResult: unknown[] = []
|
|
11
|
+
let nextFirstResult: unknown = null
|
|
12
|
+
|
|
13
|
+
const db = {
|
|
14
|
+
_calls: calls,
|
|
15
|
+
_setAllResult(rows: unknown[]) { nextAllResult = rows },
|
|
16
|
+
_setFirstResult(row: unknown) { nextFirstResult = row },
|
|
17
|
+
prepare(sql: string) {
|
|
18
|
+
let boundValues: unknown[] = []
|
|
19
|
+
const stmt = {
|
|
20
|
+
bind(...args: unknown[]) { boundValues = args; return stmt },
|
|
21
|
+
async run() {
|
|
22
|
+
calls.push({ sql, bindings: boundValues })
|
|
23
|
+
return { success: true }
|
|
24
|
+
},
|
|
25
|
+
async all() {
|
|
26
|
+
calls.push({ sql, bindings: boundValues })
|
|
27
|
+
return { results: nextAllResult }
|
|
28
|
+
},
|
|
29
|
+
async first() {
|
|
30
|
+
calls.push({ sql, bindings: boundValues })
|
|
31
|
+
return nextFirstResult
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
return stmt
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
return db as unknown as D1Database & typeof db
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const fixtureRow = {
|
|
41
|
+
id: 'abc',
|
|
42
|
+
seed_slug: 'posts',
|
|
43
|
+
name: 'test',
|
|
44
|
+
enabled: 1,
|
|
45
|
+
triggers: JSON.stringify([{ event: 'create' }]),
|
|
46
|
+
trigger_conditions: null,
|
|
47
|
+
actions: JSON.stringify([{ type: 'webhook', url: 'https://example.com' }]),
|
|
48
|
+
created_at: 1000,
|
|
49
|
+
updated_at: 1000,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('D1AutomationRepository.create', () => {
|
|
53
|
+
it('serialises actions and trigger_conditions to JSON', async () => {
|
|
54
|
+
const db = makeDb()
|
|
55
|
+
const repo = new D1AutomationRepository(db)
|
|
56
|
+
const whenNode = {
|
|
57
|
+
kind: 'group' as const,
|
|
58
|
+
op: 'AND' as const,
|
|
59
|
+
children: [{ kind: 'predicate' as const, left: { kind: 'ref' as const, key: 'this.status' }, op: 'eq' as const, right: { kind: 'literal' as const, value: 'published' } }],
|
|
60
|
+
}
|
|
61
|
+
await repo.create({
|
|
62
|
+
seed_slug: 'posts',
|
|
63
|
+
name: 'test',
|
|
64
|
+
triggers: [{ event: 'create' }],
|
|
65
|
+
trigger_conditions: whenNode,
|
|
66
|
+
actions: [{ type: 'webhook', url: 'https://example.com' }],
|
|
67
|
+
})
|
|
68
|
+
expect(db._calls).toHaveLength(1)
|
|
69
|
+
const { bindings } = db._calls[0]
|
|
70
|
+
// triggers is index 3, trigger_conditions is index 4, actions is index 5
|
|
71
|
+
expect(typeof bindings[3]).toBe('string')
|
|
72
|
+
expect(JSON.parse(bindings[3] as string)).toEqual([{ event: 'create' }])
|
|
73
|
+
expect(typeof bindings[4]).toBe('string')
|
|
74
|
+
expect(JSON.parse(bindings[4] as string)).toEqual(whenNode)
|
|
75
|
+
expect(typeof bindings[5]).toBe('string')
|
|
76
|
+
expect(JSON.parse(bindings[5] as string)).toEqual([{ type: 'webhook', url: 'https://example.com' }])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('stores null for missing trigger_conditions', async () => {
|
|
80
|
+
const db = makeDb()
|
|
81
|
+
const repo = new D1AutomationRepository(db)
|
|
82
|
+
await repo.create({
|
|
83
|
+
seed_slug: 'posts',
|
|
84
|
+
name: 'test',
|
|
85
|
+
triggers: [{ event: 'create' }],
|
|
86
|
+
trigger_conditions: null,
|
|
87
|
+
actions: [{ type: 'webhook', url: 'https://example.com' }],
|
|
88
|
+
})
|
|
89
|
+
const { bindings } = db._calls[0]
|
|
90
|
+
expect(bindings[4]).toBeNull()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('D1AutomationRepository.update', () => {
|
|
95
|
+
it('builds SET clause only from provided keys', async () => {
|
|
96
|
+
const db = makeDb()
|
|
97
|
+
const repo = new D1AutomationRepository(db)
|
|
98
|
+
await repo.update('abc', { name: 'renamed' })
|
|
99
|
+
expect(db._calls).toHaveLength(1)
|
|
100
|
+
expect(db._calls[0].sql).toContain('name = ?')
|
|
101
|
+
expect(db._calls[0].sql).not.toContain('seed_slug')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('short-circuits when no fields provided', async () => {
|
|
105
|
+
const db = makeDb()
|
|
106
|
+
const repo = new D1AutomationRepository(db)
|
|
107
|
+
await repo.update('abc', {})
|
|
108
|
+
expect(db._calls).toHaveLength(0)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('D1AutomationRepository.toggle', () => {
|
|
113
|
+
it('only writes enabled and updated_at', async () => {
|
|
114
|
+
const db = makeDb()
|
|
115
|
+
const repo = new D1AutomationRepository(db)
|
|
116
|
+
await repo.toggle('abc', false)
|
|
117
|
+
expect(db._calls).toHaveLength(1)
|
|
118
|
+
const { sql, bindings } = db._calls[0]
|
|
119
|
+
expect(sql).toBe('UPDATE automations SET enabled = ?, updated_at = ? WHERE id = ?')
|
|
120
|
+
expect(bindings[0]).toBe(0)
|
|
121
|
+
expect(bindings[2]).toBe('abc')
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('rowToAutomation JSON round-trip', () => {
|
|
126
|
+
it('deserialises trigger_conditions as null when column is null', async () => {
|
|
127
|
+
const db = makeDb()
|
|
128
|
+
db._setAllResult([fixtureRow])
|
|
129
|
+
const repo = new D1AutomationRepository(db)
|
|
130
|
+
const rows = await repo.list('posts')
|
|
131
|
+
expect(rows[0].trigger_conditions).toBeNull()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('deserialises trigger_conditions as WhenNode when column contains WhenNode JSON', async () => {
|
|
135
|
+
const db = makeDb()
|
|
136
|
+
const whenNode = {
|
|
137
|
+
kind: 'group',
|
|
138
|
+
op: 'AND',
|
|
139
|
+
children: [{
|
|
140
|
+
kind: 'predicate',
|
|
141
|
+
left: { kind: 'ref', key: 'this.status' },
|
|
142
|
+
op: 'eq',
|
|
143
|
+
right: { kind: 'literal', value: 'published' },
|
|
144
|
+
}],
|
|
145
|
+
}
|
|
146
|
+
const rowWithConditions = {
|
|
147
|
+
...fixtureRow,
|
|
148
|
+
trigger_conditions: JSON.stringify(whenNode),
|
|
149
|
+
}
|
|
150
|
+
db._setAllResult([rowWithConditions])
|
|
151
|
+
const repo = new D1AutomationRepository(db)
|
|
152
|
+
const rows = await repo.list('posts')
|
|
153
|
+
expect(rows[0].trigger_conditions).toEqual(whenNode)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('deserialises actions as parsed array', async () => {
|
|
157
|
+
const db = makeDb()
|
|
158
|
+
db._setAllResult([fixtureRow])
|
|
159
|
+
const repo = new D1AutomationRepository(db)
|
|
160
|
+
const rows = await repo.list('posts')
|
|
161
|
+
expect(rows[0].actions).toEqual([{ type: 'webhook', url: 'https://example.com' }])
|
|
162
|
+
})
|
|
163
|
+
})
|