@adula/kit 0.2.0-alpha.4 → 1.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/README.md +12 -2
- package/build/agent/AGENTS.template.md +2 -2
- package/build/agent/capabilities.md +54 -7
- package/build/agent/skills/adula-frontend-design/SKILL.md +1 -1
- package/build/agent/skills/idea-review/SKILL.md +26 -1
- package/build/agent/skills/module-review/SKILL.md +22 -0
- package/build/agent/skills/perf-review/SKILL.md +24 -0
- package/build/agent/skills/schema-review/SKILL.md +22 -0
- package/build/agent/skills/security-review/SKILL.md +24 -0
- package/build/agent/skills/ui-review/SKILL.md +22 -0
- package/build/commands/capabilities.d.ts +4 -0
- package/build/commands/capabilities.js +35 -4
- package/build/commands/doctor.js +37 -1
- package/build/commands/gaps.js +16 -6
- package/build/commands/install.js +19 -5
- package/build/commands/main.d.ts +4 -2
- package/build/commands/main.js +2 -0
- package/build/commands/module_add.js +2 -2
- package/build/commands/resource.js +1 -1
- package/build/commands/resource_snapshot.d.ts +15 -0
- package/build/commands/resource_snapshot.js +61 -0
- package/build/database/migrations/1770000000004_kit_collaboration.d.ts +5 -0
- package/build/database/migrations/1770000000004_kit_collaboration.js +10 -0
- package/build/database/migrations/1770000000005_kit_assignments.d.ts +5 -0
- package/build/database/migrations/1770000000005_kit_assignments.js +10 -0
- package/build/database/migrations/1770000000006_kit_messaging.d.ts +5 -0
- package/build/database/migrations/1770000000006_kit_messaging.js +10 -0
- package/build/database/migrations/1770000000007_kit_webhooks.d.ts +5 -0
- package/build/database/migrations/1770000000007_kit_webhooks.js +10 -0
- package/build/database/migrations/1770000000008_kit_imports.d.ts +5 -0
- package/build/database/migrations/1770000000008_kit_imports.js +10 -0
- package/build/database/migrations/1770000000010_kit_workflows.d.ts +5 -0
- package/build/database/migrations/1770000000010_kit_workflows.js +10 -0
- package/build/database/migrations/1770000000011_kit_managed_assignments.d.ts +5 -0
- package/build/database/migrations/1770000000011_kit_managed_assignments.js +10 -0
- package/build/database/migrations/1770000000012_kit_role_keys.d.ts +5 -0
- package/build/database/migrations/1770000000012_kit_role_keys.js +10 -0
- package/build/database/migrations/1770000000013_kit_notification_targets.d.ts +5 -0
- package/build/database/migrations/1770000000013_kit_notification_targets.js +10 -0
- package/build/database/migrations/1770000000014_kit_upload_grants.d.ts +5 -0
- package/build/database/migrations/1770000000014_kit_upload_grants.js +10 -0
- package/build/database/migrations/1770000000015_kit_inbound_webhooks.d.ts +5 -0
- package/build/database/migrations/1770000000015_kit_inbound_webhooks.js +10 -0
- package/build/index.d.ts +31 -2
- package/build/index.js +17 -2
- package/build/src/admin/contracts.d.ts +7 -0
- package/build/src/admin/contracts.js +34 -12
- package/build/src/admin/controller.d.ts +2 -0
- package/build/src/admin/controller.js +52 -1
- package/build/src/admin/presentation.d.ts +6 -0
- package/build/src/admin/record_title.d.ts +14 -0
- package/build/src/admin/record_title.js +50 -0
- package/build/src/admin/resource_service.d.ts +184 -2
- package/build/src/admin/resource_service.js +713 -48
- package/build/src/attachments/attachment_service.d.ts +12 -0
- package/build/src/attachments/attachment_service.js +28 -2
- package/build/src/attachments/upload_grants.d.ts +44 -0
- package/build/src/attachments/upload_grants.js +105 -0
- package/build/src/auth/ability.d.ts +1 -1
- package/build/src/auth/ability.js +4 -1
- package/build/src/auth/actor_store.js +6 -1
- package/build/src/auth/conditions.d.ts +15 -0
- package/build/src/auth/conditions.js +36 -0
- package/build/src/auth/sql.js +6 -2
- package/build/src/collaboration/assignments.d.ts +129 -0
- package/build/src/collaboration/assignments.js +333 -0
- package/build/src/collaboration/record_collaboration.d.ts +86 -0
- package/build/src/collaboration/record_collaboration.js +348 -0
- package/build/src/commands/agent_assets.js +5 -0
- package/build/src/commands/capabilities.d.ts +19 -0
- package/build/src/commands/capabilities.js +179 -0
- package/build/src/commands/doctor.d.ts +31 -0
- package/build/src/commands/doctor.js +114 -0
- package/build/src/commands/gap_report.d.ts +50 -2
- package/build/src/commands/gap_report.js +102 -4
- package/build/src/commands/generator.js +3 -3
- package/build/src/commands/snapshot.d.ts +28 -0
- package/build/src/commands/snapshot.js +48 -0
- package/build/src/commands/source_markers.d.ts +10 -1
- package/build/src/commands/source_markers.js +36 -4
- package/build/src/core/administration_guard.js +3 -1
- package/build/src/core/message_templates.d.ts +83 -0
- package/build/src/core/message_templates.js +293 -0
- package/build/src/core/module_seed.d.ts +15 -0
- package/build/src/core/module_seed.js +31 -0
- package/build/src/core/notifications.d.ts +18 -1
- package/build/src/core/notifications.js +27 -1
- package/build/src/core/roles.d.ts +27 -1
- package/build/src/core/roles.js +133 -5
- package/build/src/database/schema.d.ts +45 -0
- package/build/src/database/schema.js +290 -0
- package/build/src/eslint/index.js +26 -0
- package/build/src/events/outbox.d.ts +1 -0
- package/build/src/events/outbox.js +1 -1
- package/build/src/events/record_mutation.d.ts +11 -0
- package/build/src/events/record_mutation.js +15 -2
- package/build/src/integrations/imports.d.ts +74 -0
- package/build/src/integrations/imports.js +333 -0
- package/build/src/integrations/inbound_webhooks.d.ts +94 -0
- package/build/src/integrations/inbound_webhooks.js +276 -0
- package/build/src/integrations/openapi.d.ts +39 -0
- package/build/src/integrations/openapi.js +323 -0
- package/build/src/integrations/print.d.ts +37 -0
- package/build/src/integrations/print.js +124 -0
- package/build/src/integrations/webhooks.d.ts +98 -0
- package/build/src/integrations/webhooks.js +298 -0
- package/build/src/resource/define_resource.d.ts +1 -0
- package/build/src/resource/define_resource.js +21 -1
- package/build/src/resource/registry.d.ts +3 -0
- package/build/src/resource/registry.js +42 -0
- package/build/src/resource/types.d.ts +67 -1
- package/build/src/resource/values.js +2 -1
- package/build/src/services/settings.d.ts +13 -1
- package/build/src/services/settings.js +9 -2
- package/build/src/workflows/define_workflow.d.ts +124 -0
- package/build/src/workflows/define_workflow.js +123 -0
- package/build/src/workflows/engine.d.ts +141 -0
- package/build/src/workflows/engine.js +752 -0
- package/build/stubs/resource_contract.txt +103 -41
- package/package.json +7 -3
|
@@ -5,7 +5,7 @@ import db from '@adonisjs/lucid/services/db'
|
|
|
5
5
|
import { kit } from '#services/kit'
|
|
6
6
|
import { randomUUID } from 'node:crypto'
|
|
7
7
|
import { columnName } from '@adula/kit'
|
|
8
|
-
import type { Field, RecordData, Resource, SerializedRecord } from '@adula/kit'
|
|
8
|
+
import type { Action, Field, RecordData, Resource, SerializedRecord } from '@adula/kit'
|
|
9
9
|
|
|
10
10
|
export type FixtureContext = { userId: number; orgUnitId: number; unique: string }
|
|
11
11
|
export type ContractFixture = {
|
|
@@ -16,9 +16,12 @@ export type ContractFixture = {
|
|
|
16
16
|
stored?: RecordData
|
|
17
17
|
/** All live child rows after creation, ordered by ID, using public field names. */
|
|
18
18
|
inline?: Record<string, RecordData[]>
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* A valid update for this same record, including required validator fields. Required
|
|
21
|
+
* when the resource declares the `update` action.
|
|
22
|
+
*/
|
|
23
|
+
update?: RecordData
|
|
24
|
+
updated?: SerializedRecord
|
|
22
25
|
updatedStored?: RecordData
|
|
23
26
|
updatedInline?: Record<string, RecordData[]>
|
|
24
27
|
}
|
|
@@ -48,8 +51,13 @@ export function resourceContract(name: string, fixture: ResourceFixture) {
|
|
|
48
51
|
let resource: Resource
|
|
49
52
|
const base = `/resources/${name}`
|
|
50
53
|
const fresh = () => fixture({ userId: writer.id, orgUnitId, unique: randomUUID() })
|
|
51
|
-
const input = (values: RecordData) => ({
|
|
54
|
+
const input = (values: RecordData) => ({
|
|
55
|
+
...values,
|
|
56
|
+
...(resource.scoped && !resource.scope ? { orgUnitId } : {}),
|
|
57
|
+
})
|
|
52
58
|
const version = (row: SerializedRecord) => (resource.version ? { version: row.version } : {})
|
|
59
|
+
// Actions the resource does not declare are refused even to a role that manages all.
|
|
60
|
+
const allows = (action: Action) => resource.actions.includes(action)
|
|
53
61
|
|
|
54
62
|
group.setup(async () => {
|
|
55
63
|
const knex = db.connection().getWriteClient()
|
|
@@ -124,10 +132,27 @@ export function resourceContract(name: string, fixture: ResourceFixture) {
|
|
|
124
132
|
const response = await client.get(base).header('Accept', 'application/json')
|
|
125
133
|
response.assertStatus(401)
|
|
126
134
|
})
|
|
135
|
+
test('undeclared actions are refused to a role that manages all resources', async ({
|
|
136
|
+
client,
|
|
137
|
+
}) => {
|
|
138
|
+
for (const [action, method, suffix] of [
|
|
139
|
+
['create', 'post', ''],
|
|
140
|
+
['update', 'patch', '/999999'],
|
|
141
|
+
['delete', 'delete', '/999999'],
|
|
142
|
+
] as const) {
|
|
143
|
+
if (allows(action)) continue
|
|
144
|
+
const response = await client[method](`${base}${suffix}`)
|
|
145
|
+
.loginAs(writer)
|
|
146
|
+
.withCsrfToken()
|
|
147
|
+
.header('Accept', 'application/json')
|
|
148
|
+
response.assertStatus(403)
|
|
149
|
+
}
|
|
150
|
+
})
|
|
127
151
|
test('existing records enforce scope on reads and writes; central resources stay shared', async ({
|
|
128
152
|
client,
|
|
129
153
|
assert,
|
|
130
154
|
}) => {
|
|
155
|
+
if (!allows('create')) return
|
|
131
156
|
const values = await fresh()
|
|
132
157
|
const created = await client
|
|
133
158
|
.post(base)
|
|
@@ -159,21 +184,21 @@ export function resourceContract(name: string, fixture: ResourceFixture) {
|
|
|
159
184
|
.get(`${base}/${row.id}/edit`)
|
|
160
185
|
.loginAs(outsider)
|
|
161
186
|
.header('Accept', 'application/json')
|
|
162
|
-
edit.assertStatus(404)
|
|
187
|
+
edit.assertStatus(allows('update') ? 404 : 403)
|
|
163
188
|
const changed = await client
|
|
164
189
|
.patch(`${base}/${row.id}`)
|
|
165
190
|
.loginAs(outsider)
|
|
166
191
|
.withCsrfToken()
|
|
167
192
|
.header('Accept', 'application/json')
|
|
168
|
-
.json({ ...values.update, ...version(row) })
|
|
169
|
-
changed.assertStatus(404)
|
|
193
|
+
.json({ ...(values.update ?? values.input), ...version(row) })
|
|
194
|
+
changed.assertStatus(allows('update') ? 404 : 403)
|
|
170
195
|
const deleted = await client
|
|
171
196
|
.delete(`${base}/${row.id}`)
|
|
172
197
|
.loginAs(outsider)
|
|
173
198
|
.withCsrfToken()
|
|
174
199
|
.header('Accept', 'application/json')
|
|
175
200
|
.json(version(row))
|
|
176
|
-
deleted.assertStatus(404)
|
|
201
|
+
deleted.assertStatus(allows('delete') ? 404 : 403)
|
|
177
202
|
const after = await client
|
|
178
203
|
.get(`${base}/${row.id}`)
|
|
179
204
|
.loginAs(writer)
|
|
@@ -185,6 +210,7 @@ export function resourceContract(name: string, fixture: ResourceFixture) {
|
|
|
185
210
|
client,
|
|
186
211
|
assert,
|
|
187
212
|
}) => {
|
|
213
|
+
if (!allows('create')) return
|
|
188
214
|
const values = await fresh()
|
|
189
215
|
for (const key of resource.form)
|
|
190
216
|
assert.property(values.input, key, `Missing ${name}.${key} fixture`)
|
|
@@ -268,64 +294,99 @@ export function resourceContract(name: string, fixture: ResourceFixture) {
|
|
|
268
294
|
if (field.permissionLevel || resource.hidden?.includes(key))
|
|
269
295
|
assert.notProperty(shown.body().data, key)
|
|
270
296
|
}
|
|
271
|
-
|
|
272
|
-
|
|
297
|
+
const stored = async () => db.connection().getWriteClient()(name).where('id', row.id).first()
|
|
298
|
+
const inserted = await stored()
|
|
299
|
+
assert.equal(inserted.created_by, writer.id)
|
|
300
|
+
if (!allows('update')) {
|
|
301
|
+
const refused = await client
|
|
273
302
|
.patch(`${base}/${row.id}`)
|
|
274
303
|
.loginAs(writer)
|
|
275
304
|
.withCsrfToken()
|
|
276
305
|
.header('Accept', 'application/json')
|
|
277
|
-
.json({ ...values.
|
|
278
|
-
|
|
279
|
-
assert.equal(invalid.body().error.code, 'E_FIELD_NOT_WRITABLE')
|
|
306
|
+
.json({ ...values.input, ...version(row) })
|
|
307
|
+
refused.assertStatus(403)
|
|
280
308
|
}
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
.header('Accept', 'application/json')
|
|
286
|
-
.json({ ...values.update, ...version(row) })
|
|
287
|
-
updated.assertStatus(200)
|
|
288
|
-
for (const [key, value] of Object.entries(values.updated))
|
|
289
|
-
assert.deepEqual(updated.body().data[key], value, key)
|
|
290
|
-
await assertStored(values.update, values.updated, values.updatedStored, values.updatedInline)
|
|
291
|
-
if (resource.version) {
|
|
292
|
-
assert.equal(updated.body().data.version, Number(row.version) + 1)
|
|
293
|
-
const stale = await client
|
|
294
|
-
.patch(`${base}/${row.id}`)
|
|
309
|
+
const current = allows('update') ? await updateRecord() : row
|
|
310
|
+
if (!allows('delete')) {
|
|
311
|
+
const refused = await client
|
|
312
|
+
.delete(`${base}/${row.id}`)
|
|
295
313
|
.loginAs(writer)
|
|
296
314
|
.withCsrfToken()
|
|
297
315
|
.header('Accept', 'application/json')
|
|
298
|
-
.json(
|
|
299
|
-
|
|
316
|
+
.json(version(current))
|
|
317
|
+
refused.assertStatus(403)
|
|
318
|
+
const kept = await client
|
|
319
|
+
.get(`${base}/${row.id}`)
|
|
320
|
+
.loginAs(writer)
|
|
321
|
+
.header('Accept', 'application/json')
|
|
322
|
+
kept.assertStatus(200)
|
|
323
|
+
assert.deepEqual(kept.body().data, current)
|
|
324
|
+
return
|
|
300
325
|
}
|
|
301
|
-
const persisted = await client
|
|
302
|
-
.get(`${base}/${row.id}`)
|
|
303
|
-
.loginAs(writer)
|
|
304
|
-
.header('Accept', 'application/json')
|
|
305
|
-
persisted.assertStatus(200)
|
|
306
|
-
assert.deepEqual(persisted.body().data, updated.body().data)
|
|
307
326
|
const removed = await client
|
|
308
327
|
.delete(`${base}/${row.id}`)
|
|
309
328
|
.loginAs(writer)
|
|
310
329
|
.withCsrfToken()
|
|
311
330
|
.header('Accept', 'application/json')
|
|
312
|
-
.json(version(
|
|
331
|
+
.json(version(current))
|
|
313
332
|
removed.assertStatus(200)
|
|
314
|
-
const
|
|
315
|
-
assert.exists(
|
|
316
|
-
assert.equal(stored.created_by, writer.id)
|
|
333
|
+
const deleted = await stored()
|
|
334
|
+
assert.exists(deleted.deleted_at)
|
|
317
335
|
const missing = await client
|
|
318
336
|
.get(`${base}/${row.id}`)
|
|
319
337
|
.loginAs(writer)
|
|
320
338
|
.header('Accept', 'application/json')
|
|
321
339
|
missing.assertStatus(404)
|
|
340
|
+
|
|
341
|
+
async function updateRecord() {
|
|
342
|
+
assert.exists(values.update, `Missing ${name} update fixture`)
|
|
343
|
+
assert.exists(values.updated, `Missing ${name} updated fixture`)
|
|
344
|
+
const update = values.update!
|
|
345
|
+
for (const key of ['createdBy', 'updatedBy', 'deletedAt', 'orgPath', 'searchVector']) {
|
|
346
|
+
const invalid = await client
|
|
347
|
+
.patch(`${base}/${row.id}`)
|
|
348
|
+
.loginAs(writer)
|
|
349
|
+
.withCsrfToken()
|
|
350
|
+
.header('Accept', 'application/json')
|
|
351
|
+
.json({ ...update, ...version(row), [key]: writer.id })
|
|
352
|
+
invalid.assertStatus(422)
|
|
353
|
+
assert.equal(invalid.body().error.code, 'E_FIELD_NOT_WRITABLE')
|
|
354
|
+
}
|
|
355
|
+
const updated = await client
|
|
356
|
+
.patch(`${base}/${row.id}`)
|
|
357
|
+
.loginAs(writer)
|
|
358
|
+
.withCsrfToken()
|
|
359
|
+
.header('Accept', 'application/json')
|
|
360
|
+
.json({ ...update, ...version(row) })
|
|
361
|
+
updated.assertStatus(200)
|
|
362
|
+
for (const [key, value] of Object.entries(values.updated!))
|
|
363
|
+
assert.deepEqual(updated.body().data[key], value, key)
|
|
364
|
+
await assertStored(update, values.updated!, values.updatedStored, values.updatedInline)
|
|
365
|
+
if (resource.version) {
|
|
366
|
+
assert.equal(updated.body().data.version, Number(row.version) + 1)
|
|
367
|
+
const stale = await client
|
|
368
|
+
.patch(`${base}/${row.id}`)
|
|
369
|
+
.loginAs(writer)
|
|
370
|
+
.withCsrfToken()
|
|
371
|
+
.header('Accept', 'application/json')
|
|
372
|
+
.json({ ...update, ...version(row) })
|
|
373
|
+
stale.assertStatus(409)
|
|
374
|
+
}
|
|
375
|
+
const persisted = await client
|
|
376
|
+
.get(`${base}/${row.id}`)
|
|
377
|
+
.loginAs(writer)
|
|
378
|
+
.header('Accept', 'application/json')
|
|
379
|
+
persisted.assertStatus(200)
|
|
380
|
+
assert.deepEqual(persisted.body().data, updated.body().data)
|
|
381
|
+
return updated.body().data as SerializedRecord
|
|
382
|
+
}
|
|
322
383
|
})
|
|
323
384
|
test('unique constraints reject duplicates and allow reuse after soft deletion', async ({
|
|
324
385
|
client,
|
|
325
386
|
assert,
|
|
326
387
|
}) => {
|
|
327
388
|
const keys = Object.entries(resource.fields).filter(([, field]) => field.unique)
|
|
328
|
-
if (!keys.length) return
|
|
389
|
+
if (!keys.length || !allows('create')) return
|
|
329
390
|
const values = await fresh()
|
|
330
391
|
const created = await client
|
|
331
392
|
.post(base)
|
|
@@ -369,6 +430,7 @@ export function resourceContract(name: string, fixture: ResourceFixture) {
|
|
|
369
430
|
rejected.assertStatus(409)
|
|
370
431
|
assert.equal(rejected.body().error.code, 'E_DUPLICATE')
|
|
371
432
|
}
|
|
433
|
+
if (!allows('delete')) return
|
|
372
434
|
const removed = await client
|
|
373
435
|
.delete(`${base}/${row.id}`)
|
|
374
436
|
.loginAs(writer)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adula/kit",
|
|
3
3
|
"description": "Resource-driven business applications for AdonisJS 7",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.0.0"
|
|
7
7
|
},
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@jrmc/adonis-mcp": "2.0.0",
|
|
46
46
|
"@poppinss/ts-exec": "1.4.4",
|
|
47
47
|
"@types/node": "26.2.0",
|
|
48
|
+
"@types/papaparse": "5.5.2",
|
|
48
49
|
"eslint": "10.9.0",
|
|
49
50
|
"pg": "8.16.3",
|
|
50
51
|
"typescript": "6.0.3"
|
|
@@ -74,7 +75,9 @@
|
|
|
74
75
|
"prettier": "@adonisjs/prettier-config",
|
|
75
76
|
"dependencies": {
|
|
76
77
|
"@casl/ability": "7.0.1",
|
|
77
|
-
"knex": "3.3.0"
|
|
78
|
+
"knex": "3.3.0",
|
|
79
|
+
"papaparse": "5.7.0",
|
|
80
|
+
"xstate": "5.33.2"
|
|
78
81
|
},
|
|
79
82
|
"repository": {
|
|
80
83
|
"type": "git",
|
|
@@ -89,6 +92,7 @@
|
|
|
89
92
|
"build": "node ../../scripts/clean-kit-build.mjs && tsc -p tsconfig.build.json && node ../../scripts/copy-kit-assets.mjs",
|
|
90
93
|
"typecheck": "tsc --noEmit",
|
|
91
94
|
"test": "node --import=@poppinss/ts-exec bin/test.ts",
|
|
92
|
-
"lint": "eslint src providers commands database tests index.ts"
|
|
95
|
+
"lint": "eslint src providers commands database tests index.ts",
|
|
96
|
+
"capabilities": "node --import=@poppinss/ts-exec bin/generate_capabilities.ts"
|
|
93
97
|
}
|
|
94
98
|
}
|