@adula/kit 0.2.0-alpha.1

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 (141) hide show
  1. package/LICENSE.md +10 -0
  2. package/README.md +31 -0
  3. package/build/agent/AGENTS.template.md +28 -0
  4. package/build/agent/capabilities.md +13 -0
  5. package/build/agent/skills/adula-frontend-design/SKILL.md +59 -0
  6. package/build/agent/skills/idea-review/SKILL.md +6 -0
  7. package/build/commands/capabilities.d.ts +6 -0
  8. package/build/commands/capabilities.js +9 -0
  9. package/build/commands/doctor.d.ts +9 -0
  10. package/build/commands/doctor.js +32 -0
  11. package/build/commands/gaps.d.ts +12 -0
  12. package/build/commands/gaps.js +46 -0
  13. package/build/commands/install.d.ts +9 -0
  14. package/build/commands/install.js +91 -0
  15. package/build/commands/main.d.ts +6 -0
  16. package/build/commands/main.js +27 -0
  17. package/build/commands/module_add.d.ts +8 -0
  18. package/build/commands/module_add.js +57 -0
  19. package/build/commands/module_remove.d.ts +10 -0
  20. package/build/commands/module_remove.js +94 -0
  21. package/build/commands/resource.d.ts +8 -0
  22. package/build/commands/resource.js +29 -0
  23. package/build/commands/storage_migrate.d.ts +12 -0
  24. package/build/commands/storage_migrate.js +39 -0
  25. package/build/commands/ui.d.ts +11 -0
  26. package/build/commands/ui.js +210 -0
  27. package/build/configure.d.ts +2 -0
  28. package/build/configure.js +79 -0
  29. package/build/database/migrations/1770000000000_kit_core.d.ts +5 -0
  30. package/build/database/migrations/1770000000000_kit_core.js +10 -0
  31. package/build/database/migrations/1770000000001_kit_attachments.d.ts +5 -0
  32. package/build/database/migrations/1770000000001_kit_attachments.js +10 -0
  33. package/build/database/migrations/1770000000002_kit_saved_views.d.ts +5 -0
  34. package/build/database/migrations/1770000000002_kit_saved_views.js +10 -0
  35. package/build/database/migrations/1770000000003_kit_user_invitations.d.ts +5 -0
  36. package/build/database/migrations/1770000000003_kit_user_invitations.js +18 -0
  37. package/build/index.d.ts +51 -0
  38. package/build/index.js +34 -0
  39. package/build/providers/kit_provider.d.ts +6 -0
  40. package/build/providers/kit_provider.js +10 -0
  41. package/build/src/admin/contracts.d.ts +12 -0
  42. package/build/src/admin/contracts.js +93 -0
  43. package/build/src/admin/controller.d.ts +30 -0
  44. package/build/src/admin/controller.js +116 -0
  45. package/build/src/admin/errors.d.ts +5 -0
  46. package/build/src/admin/errors.js +9 -0
  47. package/build/src/admin/presentation.d.ts +22 -0
  48. package/build/src/admin/presentation.js +1 -0
  49. package/build/src/admin/resource_service.d.ts +170 -0
  50. package/build/src/admin/resource_service.js +763 -0
  51. package/build/src/attachments/attachment_service.d.ts +71 -0
  52. package/build/src/attachments/attachment_service.js +182 -0
  53. package/build/src/attachments/doctor.d.ts +4 -0
  54. package/build/src/attachments/doctor.js +28 -0
  55. package/build/src/attachments/storage_migrate.d.ts +39 -0
  56. package/build/src/attachments/storage_migrate.js +64 -0
  57. package/build/src/auth/ability.d.ts +22 -0
  58. package/build/src/auth/ability.js +31 -0
  59. package/build/src/auth/actor_store.d.ts +14 -0
  60. package/build/src/auth/actor_store.js +65 -0
  61. package/build/src/auth/conditions.d.ts +11 -0
  62. package/build/src/auth/conditions.js +81 -0
  63. package/build/src/auth/packed_rules.d.ts +4 -0
  64. package/build/src/auth/packed_rules.js +13 -0
  65. package/build/src/auth/sql.d.ts +11 -0
  66. package/build/src/auth/sql.js +114 -0
  67. package/build/src/commands/agent_assets.d.ts +7 -0
  68. package/build/src/commands/agent_assets.js +54 -0
  69. package/build/src/commands/doctor.d.ts +11 -0
  70. package/build/src/commands/doctor.js +261 -0
  71. package/build/src/commands/generator.d.ts +1 -0
  72. package/build/src/commands/generator.js +70 -0
  73. package/build/src/commands/source_markers.d.ts +2 -0
  74. package/build/src/commands/source_markers.js +9 -0
  75. package/build/src/core/activity.d.ts +43 -0
  76. package/build/src/core/activity.js +77 -0
  77. package/build/src/core/administration_guard.d.ts +3 -0
  78. package/build/src/core/administration_guard.js +37 -0
  79. package/build/src/core/health.d.ts +42 -0
  80. package/build/src/core/health.js +44 -0
  81. package/build/src/core/mail_test.d.ts +18 -0
  82. package/build/src/core/mail_test.js +88 -0
  83. package/build/src/core/notifications.d.ts +25 -0
  84. package/build/src/core/notifications.js +65 -0
  85. package/build/src/core/org_units.d.ts +30 -0
  86. package/build/src/core/org_units.js +123 -0
  87. package/build/src/core/roles.d.ts +72 -0
  88. package/build/src/core/roles.js +319 -0
  89. package/build/src/core/saved_views.d.ts +37 -0
  90. package/build/src/core/saved_views.js +139 -0
  91. package/build/src/core/settings.d.ts +29 -0
  92. package/build/src/core/settings.js +113 -0
  93. package/build/src/core/setup.d.ts +14 -0
  94. package/build/src/core/setup.js +89 -0
  95. package/build/src/core/ui_preferences.d.ts +12 -0
  96. package/build/src/core/ui_preferences.js +30 -0
  97. package/build/src/core/user_invitations.d.ts +23 -0
  98. package/build/src/core/user_invitations.js +144 -0
  99. package/build/src/core/users.d.ts +44 -0
  100. package/build/src/core/users.js +182 -0
  101. package/build/src/database/configure.d.ts +3 -0
  102. package/build/src/database/configure.js +41 -0
  103. package/build/src/database/schema.d.ts +7 -0
  104. package/build/src/database/schema.js +248 -0
  105. package/build/src/eslint/index.d.ts +3 -0
  106. package/build/src/eslint/index.js +64 -0
  107. package/build/src/events/adonis_jobs.d.ts +9 -0
  108. package/build/src/events/adonis_jobs.js +13 -0
  109. package/build/src/events/outbox.d.ts +18 -0
  110. package/build/src/events/outbox.js +33 -0
  111. package/build/src/events/record_mutation.d.ts +11 -0
  112. package/build/src/events/record_mutation.js +45 -0
  113. package/build/src/mcp/tools.d.ts +8 -0
  114. package/build/src/mcp/tools.js +156 -0
  115. package/build/src/org/org_service.d.ts +2 -0
  116. package/build/src/org/org_service.js +16 -0
  117. package/build/src/resource/define_resource.d.ts +4 -0
  118. package/build/src/resource/define_resource.js +48 -0
  119. package/build/src/resource/registry.d.ts +9 -0
  120. package/build/src/resource/registry.js +59 -0
  121. package/build/src/resource/types.d.ts +80 -0
  122. package/build/src/resource/types.js +1 -0
  123. package/build/src/resource/values.d.ts +5 -0
  124. package/build/src/resource/values.js +87 -0
  125. package/build/src/services/backup.d.ts +14 -0
  126. package/build/src/services/backup.js +59 -0
  127. package/build/src/services/settings.d.ts +9 -0
  128. package/build/src/services/settings.js +23 -0
  129. package/build/src/version.d.ts +1 -0
  130. package/build/src/version.js +2 -0
  131. package/build/stubs/README.md +6 -0
  132. package/build/stubs/controller.stub +8 -0
  133. package/build/stubs/main.d.ts +5 -0
  134. package/build/stubs/main.js +5 -0
  135. package/build/stubs/main.ts +5 -0
  136. package/build/stubs/modules.stub +6 -0
  137. package/build/stubs/resource_contract.stub +1 -0
  138. package/build/stubs/resource_contract.txt +390 -0
  139. package/build/stubs/routes.stub +16 -0
  140. package/build/stubs/service.stub +9 -0
  141. package/package.json +104 -0
@@ -0,0 +1,390 @@
1
+ import { test } from '@japa/runner'
2
+ import User from '#models/user'
3
+ import app from '@adonisjs/core/services/app'
4
+ import db from '@adonisjs/lucid/services/db'
5
+ import { kit } from '#services/kit'
6
+ import { randomUUID } from 'node:crypto'
7
+ import { columnName } from '@adula/kit'
8
+ import type { Field, RecordData, Resource, SerializedRecord } from '@adula/kit'
9
+
10
+ export type FixtureContext = { userId: number; orgUnitId: number; unique: string }
11
+ export type ContractFixture = {
12
+ input: RecordData
13
+ /** Expected public values after validation; relations have separate tests. */
14
+ expected: SerializedRecord
15
+ /** Normalized database values for writable fields excluded from the response. */
16
+ stored?: RecordData
17
+ /** All live child rows after creation, ordered by ID, using public field names. */
18
+ inline?: Record<string, RecordData[]>
19
+ /** A valid update for this same record, including required validator fields. */
20
+ update: RecordData
21
+ updated: SerializedRecord
22
+ updatedStored?: RecordData
23
+ updatedInline?: Record<string, RecordData[]>
24
+ }
25
+ export type ResourceFixture = (
26
+ context: FixtureContext
27
+ ) => Promise<ContractFixture> | ContractFixture
28
+
29
+ function storedValue(field: Field, value: unknown) {
30
+ if (!(value instanceof Date)) return value
31
+ return field.type === 'date'
32
+ ? [
33
+ value.getFullYear(),
34
+ String(value.getMonth() + 1).padStart(2, '0'),
35
+ String(value.getDate()).padStart(2, '0'),
36
+ ].join('-')
37
+ : value.toISOString()
38
+ }
39
+
40
+ /** Fixtures belong to the application and exercise its real validators and HTTP routes. */
41
+ export function resourceContract(name: string, fixture: ResourceFixture) {
42
+ test.group(`HTTP security contract: ${name}`, (group) => {
43
+ let denied: User
44
+ let writer: User
45
+ let reader: User
46
+ let outsider: User
47
+ let orgUnitId: number
48
+ let resource: Resource
49
+ const base = `/resources/${name}`
50
+ const fresh = () => fixture({ userId: writer.id, orgUnitId, unique: randomUUID() })
51
+ const input = (values: RecordData) => ({ ...values, ...(resource.scoped ? { orgUnitId } : {}) })
52
+ const version = (row: SerializedRecord) => (resource.version ? { version: row.version } : {})
53
+
54
+ group.setup(async () => {
55
+ const knex = db.connection().getWriteClient()
56
+ const databaseInfo = await knex.raw('SELECT current_database() AS name')
57
+ const database = databaseInfo.rows[0].name
58
+ if (!app.inTest || !database.endsWith('_test'))
59
+ throw new Error('Resource contracts require a dedicated *_test database')
60
+ resource = kit().registry.get(name)
61
+ const suffix = randomUUID()
62
+ const users = []
63
+ for (const label of ['denied', 'writer', 'reader', 'outside']) {
64
+ users.push(
65
+ await User.create({
66
+ fullName: 'مستخدم الاختبار',
67
+ email: `${label}-${suffix}@example.test`,
68
+ password: 'a-long-test-password-123',
69
+ })
70
+ )
71
+ }
72
+ ;[denied, writer, reader, outsider] = users
73
+ const [org, outside] = await knex('org_units')
74
+ .insert([
75
+ { name: 'نطاق الاختبار', type: 'root', path: `contract_${suffix.replaceAll('-', '_')}` },
76
+ { name: 'نطاق آخر', type: 'root', path: `outside_${suffix.replaceAll('-', '_')}` },
77
+ ])
78
+ .returning('id')
79
+ orgUnitId = org.id
80
+ const [writeRole, readRole] = await knex('roles')
81
+ .insert([
82
+ { name: `writer-${suffix}`, permission_level: 1 },
83
+ { name: `reader-${suffix}`, permission_level: 0 },
84
+ ])
85
+ .returning('id')
86
+ await knex('role_rules').insert([
87
+ { role_id: writeRole.id, subject: 'all', action: 'manage' },
88
+ { role_id: readRole.id, subject: 'all', action: 'view' },
89
+ ])
90
+ await knex('user_roles').insert([
91
+ { user_id: writer.id, role_id: writeRole.id },
92
+ { user_id: outsider.id, role_id: writeRole.id },
93
+ { user_id: reader.id, role_id: readRole.id },
94
+ ])
95
+ await knex('user_org_units').insert([
96
+ { user_id: writer.id, org_unit_id: orgUnitId },
97
+ { user_id: reader.id, org_unit_id: orgUnitId },
98
+ { user_id: outsider.id, org_unit_id: outside.id },
99
+ ])
100
+ })
101
+
102
+ for (const [method, suffix] of [
103
+ ['get', ''],
104
+ ['get', '/create'],
105
+ ['get', '/999999/edit'],
106
+ ['get', '/999999'],
107
+ ['post', ''],
108
+ ['patch', '/999999'],
109
+ ['delete', '/999999'],
110
+ ['post', '/999999/submit'],
111
+ ['post', '/999999/cancel'],
112
+ ] as const) {
113
+ test(`${method.toUpperCase()} ${suffix || '/'} returns 403 without roles`, async ({
114
+ client,
115
+ }) => {
116
+ const response = await client[method](`${base}${suffix}`)
117
+ .loginAs(denied)
118
+ .withCsrfToken()
119
+ .header('Accept', 'application/json')
120
+ response.assertStatus(403)
121
+ })
122
+ }
123
+ test('resource route is protected from anonymous access', async ({ client }) => {
124
+ const response = await client.get(base).header('Accept', 'application/json')
125
+ response.assertStatus(401)
126
+ })
127
+ test('existing records enforce scope on reads and writes; central resources stay shared', async ({
128
+ client,
129
+ assert,
130
+ }) => {
131
+ const values = await fresh()
132
+ const created = await client
133
+ .post(base)
134
+ .loginAs(writer)
135
+ .withCsrfToken()
136
+ .header('Accept', 'application/json')
137
+ .json(input(values.input))
138
+ created.assertStatus(201)
139
+ const row = created.body().data
140
+ const visible = await client
141
+ .get(`${base}/${row.id}`)
142
+ .loginAs(writer)
143
+ .header('Accept', 'application/json')
144
+ visible.assertStatus(200)
145
+ assert.equal(visible.body().data.id, row.id)
146
+ const outside = await client
147
+ .get(`${base}/${row.id}`)
148
+ .loginAs(outsider)
149
+ .header('Accept', 'application/json')
150
+ outside.assertStatus(resource.scoped ? 404 : 200)
151
+ if (!resource.scoped) return
152
+ const listing = await client.get(base).loginAs(outsider).header('Accept', 'application/json')
153
+ listing.assertStatus(200)
154
+ assert.notInclude(
155
+ listing.body().data.map((entry: SerializedRecord) => entry.id),
156
+ row.id
157
+ )
158
+ const edit = await client
159
+ .get(`${base}/${row.id}/edit`)
160
+ .loginAs(outsider)
161
+ .header('Accept', 'application/json')
162
+ edit.assertStatus(404)
163
+ const changed = await client
164
+ .patch(`${base}/${row.id}`)
165
+ .loginAs(outsider)
166
+ .withCsrfToken()
167
+ .header('Accept', 'application/json')
168
+ .json({ ...values.update, ...version(row) })
169
+ changed.assertStatus(404)
170
+ const deleted = await client
171
+ .delete(`${base}/${row.id}`)
172
+ .loginAs(outsider)
173
+ .withCsrfToken()
174
+ .header('Accept', 'application/json')
175
+ .json(version(row))
176
+ deleted.assertStatus(404)
177
+ const after = await client
178
+ .get(`${base}/${row.id}`)
179
+ .loginAs(writer)
180
+ .header('Accept', 'application/json')
181
+ after.assertStatus(200)
182
+ assert.deepEqual(after.body().data, row)
183
+ })
184
+ test('fixture fields round-trip, private values stay hidden, updates and soft deletion persist', async ({
185
+ client,
186
+ assert,
187
+ }) => {
188
+ const values = await fresh()
189
+ for (const key of resource.form)
190
+ assert.property(values.input, key, `Missing ${name}.${key} fixture`)
191
+ const created = await client
192
+ .post(base)
193
+ .loginAs(writer)
194
+ .withCsrfToken()
195
+ .header('Accept', 'application/json')
196
+ .json(input(values.input))
197
+ created.assertStatus(201)
198
+ const row = created.body().data
199
+ assert.isNumber(row.id)
200
+ for (const [key, value] of Object.entries(values.expected))
201
+ assert.deepEqual(row[key], value, key)
202
+ const exposed = new Set([
203
+ 'id',
204
+ ...(resource.serialize ?? [...resource.list, ...resource.show]),
205
+ ...(resource.scoped ? ['orgUnitId'] : []),
206
+ ...(resource.version ? ['version'] : []),
207
+ ...(resource.submittable ? ['docStatus'] : []),
208
+ ])
209
+ for (const key of Object.keys(row))
210
+ assert.isTrue(exposed.has(key), `Unexpected serialized field ${key}`)
211
+ const assertStored = async (
212
+ submitted: RecordData,
213
+ expected: SerializedRecord,
214
+ stored: RecordData = {},
215
+ inline: Record<string, RecordData[]> = {}
216
+ ) => {
217
+ const persistedRow = await db
218
+ .connection()
219
+ .getWriteClient()(name)
220
+ .where('id', row.id)
221
+ .first()
222
+ const expectations = { ...expected, ...stored }
223
+ for (const key of Object.keys(submitted)) {
224
+ if (!resource.form.includes(key)) continue
225
+ const field = resource.fields[key]
226
+ if (field.type === 'hasMany') {
227
+ assert.property(inline, key, `Missing expected ${name}.${key} child rows`)
228
+ const child = kit().registry.get(field.resource)
229
+ const foreignKey = child.fields[field.foreignKey].column ?? columnName(field.foreignKey)
230
+ const rows = await db
231
+ .connection()
232
+ .getWriteClient()(child.name)
233
+ .where(foreignKey, row.id)
234
+ .whereNull('deleted_at')
235
+ .orderBy('id')
236
+ assert.lengthOf(rows, inline[key].length)
237
+ for (const [index, entry] of inline[key].entries()) {
238
+ for (const childKey of child.form.filter((entryKey) => entryKey !== field.foreignKey))
239
+ assert.property(entry, childKey, `Missing expected ${field.resource}.${childKey}`)
240
+ for (const [childKey, value] of Object.entries(entry)) {
241
+ const childField = child.fields[childKey]
242
+ assert.exists(childField, `Unknown expected child field ${childKey}`)
243
+ assert.deepEqual(
244
+ storedValue(childField, rows[index][childField.column ?? columnName(childKey)]),
245
+ value
246
+ )
247
+ }
248
+ }
249
+ } else {
250
+ assert.property(expectations, key, `Missing stored ${name}.${key} expectation`)
251
+ if (exposed.has(key))
252
+ assert.property(expected, key, `Missing public ${name}.${key} expectation`)
253
+ assert.deepEqual(
254
+ storedValue(field, persistedRow[field.column ?? columnName(key)]),
255
+ expectations[key],
256
+ key
257
+ )
258
+ }
259
+ }
260
+ }
261
+ await assertStored(values.input, values.expected, values.stored, values.inline)
262
+ const shown = await client
263
+ .get(`${base}/${row.id}`)
264
+ .loginAs(reader)
265
+ .header('Accept', 'application/json')
266
+ shown.assertStatus(200)
267
+ for (const [key, field] of Object.entries(resource.fields)) {
268
+ if (field.permissionLevel || resource.hidden?.includes(key))
269
+ assert.notProperty(shown.body().data, key)
270
+ }
271
+ for (const key of ['createdBy', 'updatedBy', 'deletedAt', 'orgPath', 'searchVector']) {
272
+ const invalid = await client
273
+ .patch(`${base}/${row.id}`)
274
+ .loginAs(writer)
275
+ .withCsrfToken()
276
+ .header('Accept', 'application/json')
277
+ .json({ ...values.update, ...version(row), [key]: writer.id })
278
+ invalid.assertStatus(422)
279
+ assert.equal(invalid.body().error.code, 'E_FIELD_NOT_WRITABLE')
280
+ }
281
+ const updated = await client
282
+ .patch(`${base}/${row.id}`)
283
+ .loginAs(writer)
284
+ .withCsrfToken()
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}`)
295
+ .loginAs(writer)
296
+ .withCsrfToken()
297
+ .header('Accept', 'application/json')
298
+ .json({ ...values.update, ...version(row) })
299
+ stale.assertStatus(409)
300
+ }
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
+ const removed = await client
308
+ .delete(`${base}/${row.id}`)
309
+ .loginAs(writer)
310
+ .withCsrfToken()
311
+ .header('Accept', 'application/json')
312
+ .json(version(updated.body().data))
313
+ removed.assertStatus(200)
314
+ const stored = await db.connection().getWriteClient()(name).where('id', row.id).first()
315
+ assert.exists(stored.deleted_at)
316
+ assert.equal(stored.created_by, writer.id)
317
+ const missing = await client
318
+ .get(`${base}/${row.id}`)
319
+ .loginAs(writer)
320
+ .header('Accept', 'application/json')
321
+ missing.assertStatus(404)
322
+ })
323
+ test('unique constraints reject duplicates and allow reuse after soft deletion', async ({
324
+ client,
325
+ assert,
326
+ }) => {
327
+ const keys = Object.entries(resource.fields).filter(([, field]) => field.unique)
328
+ if (!keys.length) return
329
+ const values = await fresh()
330
+ const created = await client
331
+ .post(base)
332
+ .loginAs(writer)
333
+ .withCsrfToken()
334
+ .header('Accept', 'application/json')
335
+ .json(input(values.input))
336
+ created.assertStatus(201)
337
+ const row = created.body().data
338
+ const knex = db.connection().getWriteClient()
339
+ const stored = await knex(name).where('id', row.id).first()
340
+ const distinct = await fresh()
341
+ const second = await client
342
+ .post(base)
343
+ .loginAs(writer)
344
+ .withCsrfToken()
345
+ .header('Accept', 'application/json')
346
+ .json(input(distinct.input))
347
+ second.assertStatus(201)
348
+ // Read-only sequence values also require real PostgreSQL uniqueness assertions.
349
+ for (const [key, field] of keys) {
350
+ const column = field.column ?? columnName(key)
351
+ assert.isNotNull(stored[column], `Missing unique fixture ${key}`)
352
+ await assert.rejects(
353
+ () =>
354
+ knex(name)
355
+ .where('id', second.body().data.id)
356
+ .update({ [column]: stored[column] }),
357
+ /duplicate key/
358
+ )
359
+ }
360
+ for (const [key, field] of keys) {
361
+ if (field.sequence || !resource.form.includes(key)) continue
362
+ const other = await fresh()
363
+ const rejected = await client
364
+ .post(base)
365
+ .loginAs(writer)
366
+ .withCsrfToken()
367
+ .header('Accept', 'application/json')
368
+ .json(input({ ...other.input, [key]: values.input[key] }))
369
+ rejected.assertStatus(409)
370
+ assert.equal(rejected.body().error.code, 'E_DUPLICATE')
371
+ }
372
+ const removed = await client
373
+ .delete(`${base}/${row.id}`)
374
+ .loginAs(writer)
375
+ .withCsrfToken()
376
+ .header('Accept', 'application/json')
377
+ .json(version(row))
378
+ removed.assertStatus(200)
379
+ for (const [key, field] of keys) {
380
+ const column = field.column ?? columnName(key)
381
+ assert.equal(
382
+ await knex(name)
383
+ .where('id', second.body().data.id)
384
+ .update({ [column]: stored[column] }),
385
+ 1
386
+ )
387
+ }
388
+ })
389
+ })
390
+ }
@@ -0,0 +1,16 @@
1
+ {{{ exports({ to: app.makePath('start/kit_routes.ts') }) }}}
2
+ import router from '@adonisjs/core/services/router'
3
+ import { middleware } from '#start/kernel'
4
+ const ResourcesController = () => import('#controllers/resources_controller')
5
+
6
+ router.group(() => {
7
+ router.get('/resources/:resource', [ResourcesController, 'index'])
8
+ router.get('/resources/:resource/create', [ResourcesController, 'create'])
9
+ router.get('/resources/:resource/:id/edit', [ResourcesController, 'edit'])
10
+ router.get('/resources/:resource/:id', [ResourcesController, 'show'])
11
+ router.post('/resources/:resource', [ResourcesController, 'store'])
12
+ router.patch('/resources/:resource/:id', [ResourcesController, 'update'])
13
+ router.delete('/resources/:resource/:id', [ResourcesController, 'destroy'])
14
+ router.post('/resources/:resource/:id/submit', [ResourcesController, 'submit'])
15
+ router.post('/resources/:resource/:id/cancel', [ResourcesController, 'cancel'])
16
+ }).use(middleware.auth())
@@ -0,0 +1,9 @@
1
+ {{{ exports({ to: app.makePath('app/services/kit.ts') }) }}}
2
+ import db from '@adonisjs/lucid/services/db'
3
+ import { ActorStore, ResourceService } from '@adula/kit'
4
+ import { registry } from '#start/modules'
5
+
6
+ export function kit() {
7
+ const knex = db.connection().getWriteClient()
8
+ return { registry, resources: new ResourceService(knex, registry), actors: new ActorStore(knex, registry) }
9
+ }
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@adula/kit",
3
+ "description": "Resource-driven business applications for AdonisJS 7",
4
+ "version": "0.2.0-alpha.1",
5
+ "engines": {
6
+ "node": ">=24.0.0"
7
+ },
8
+ "type": "module",
9
+ "files": [
10
+ "build",
11
+ "!build/bin",
12
+ "!build/tests"
13
+ ],
14
+ "main": "build/index.js",
15
+ "exports": {
16
+ ".": "./build/index.js",
17
+ "./types": "./build/src/resource/types.js",
18
+ "./auth": "./build/src/auth/ability.js",
19
+ "./mcp": "./build/src/mcp/tools.js",
20
+ "./provider": "./build/providers/kit_provider.js",
21
+ "./commands": "./build/commands/main.js",
22
+ "./eslint": "./build/src/eslint/index.js",
23
+ "./migrations/*": "./build/database/migrations/*.js",
24
+ "./package.json": "./package.json"
25
+ },
26
+ "keywords": [
27
+ "adonisjs",
28
+ "business-applications",
29
+ "arabic",
30
+ "rtl",
31
+ "postgresql",
32
+ "authorization"
33
+ ],
34
+ "author": "adula contributors",
35
+ "license": "MIT",
36
+ "devDependencies": {
37
+ "@adonisjs/assembler": "8.5.0",
38
+ "@adonisjs/core": "7.5.0",
39
+ "@adonisjs/drive": "4.0.0",
40
+ "@adonisjs/eslint-config": "3.1.0",
41
+ "@adonisjs/lucid": "22.4.2",
42
+ "@adonisjs/tsconfig": "2.0.0",
43
+ "@japa/assert": "4.2.0",
44
+ "@japa/runner": "5.3.0",
45
+ "@jrmc/adonis-mcp": "2.0.0",
46
+ "@poppinss/ts-exec": "1.4.4",
47
+ "@types/node": "26.2.0",
48
+ "@ucast/sql": "0.2.0",
49
+ "eslint": "10.9.0",
50
+ "pg": "8.16.3",
51
+ "typescript": "6.0.3"
52
+ },
53
+ "peerDependencies": {
54
+ "@adonisjs/assembler": "^8.5.0",
55
+ "@adonisjs/core": "^7.0.0",
56
+ "@adonisjs/drive": "^4.0.0",
57
+ "@adonisjs/lucid": "^22.0.0",
58
+ "@jrmc/adonis-mcp": "^2.0.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@jrmc/adonis-mcp": {
62
+ "optional": true
63
+ },
64
+ "@adonisjs/assembler": {
65
+ "optional": true
66
+ },
67
+ "@adonisjs/drive": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "publishConfig": {
72
+ "access": "public",
73
+ "provenance": true
74
+ },
75
+ "c8": {
76
+ "reporter": [
77
+ "text",
78
+ "html"
79
+ ],
80
+ "exclude": [
81
+ "tests/**"
82
+ ]
83
+ },
84
+ "prettier": "@adonisjs/prettier-config",
85
+ "dependencies": {
86
+ "@casl/ability": "7.0.1",
87
+ "knex": "3.3.0"
88
+ },
89
+ "repository": {
90
+ "type": "git",
91
+ "url": "git+https://github.com/adulash/adula-kit.git",
92
+ "directory": "packages/kit"
93
+ },
94
+ "homepage": "https://github.com/adulash/adula-kit/tree/main/packages/kit#readme",
95
+ "bugs": {
96
+ "url": "https://github.com/adulash/adula-kit/issues"
97
+ },
98
+ "scripts": {
99
+ "build": "node ../../scripts/clean-kit-build.mjs && tsc -p tsconfig.build.json && node ../../scripts/copy-kit-assets.mjs",
100
+ "typecheck": "tsc --noEmit",
101
+ "test": "node --import=@poppinss/ts-exec bin/test.ts",
102
+ "lint": "eslint src providers commands database tests index.ts"
103
+ }
104
+ }