@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,48 @@
1
+ export function identifier(value) {
2
+ if (!/^[a-z][a-z0-9_]*$/.test(value))
3
+ throw new Error(`Unsafe SQL identifier: ${value}`);
4
+ return value;
5
+ }
6
+ export function columnName(name) {
7
+ return identifier(name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`));
8
+ }
9
+ export function defineResource(input) {
10
+ identifier(input.name);
11
+ if (typeof input.scoped !== 'boolean')
12
+ throw new Error(`${input.name}: scoped must be explicit`);
13
+ if (!input.label.ar || !input.label.en)
14
+ throw new Error('Bilingual labels are required');
15
+ for (const [key, field] of Object.entries(input.fields)) {
16
+ columnName(key);
17
+ identifier(field.column ?? columnName(key));
18
+ if ([
19
+ 'id',
20
+ 'orgUnitId',
21
+ 'createdBy',
22
+ 'updatedBy',
23
+ 'createdAt',
24
+ 'updatedAt',
25
+ 'deletedAt',
26
+ 'version',
27
+ 'docStatus',
28
+ 'amendedFromId',
29
+ ].includes(key))
30
+ throw new Error(`Reserved field: ${key}`);
31
+ if (!field.label.ar || !field.label.en)
32
+ throw new Error(`${key}: bilingual labels are required`);
33
+ }
34
+ for (const keys of [
35
+ input.list,
36
+ input.form,
37
+ input.show,
38
+ input.hidden ?? [],
39
+ input.serialize ?? [],
40
+ ]) {
41
+ for (const key of keys)
42
+ if (!(key in input.fields))
43
+ throw new Error(`Unknown field: ${key}`);
44
+ }
45
+ if (!input.submittable && input.actions.some((a) => ['submit', 'cancel', 'amend'].includes(a)))
46
+ throw new Error('Document actions require submittable');
47
+ return Object.freeze({ ...input, version: input.version ?? input.submittable ?? false });
48
+ }
@@ -0,0 +1,9 @@
1
+ import type { Module, Resource } from './types.js';
2
+ export declare class ResourceRegistry {
3
+ #private;
4
+ register(modules: readonly Module[]): this;
5
+ get(name: string): Resource;
6
+ all(): Resource[];
7
+ owner(name: string): string;
8
+ modules(): Module[];
9
+ }
@@ -0,0 +1,59 @@
1
+ import { identifier } from './define_resource.js';
2
+ export class ResourceRegistry {
3
+ #resources = new Map();
4
+ #owners = new Map();
5
+ #modules = new Map();
6
+ register(modules) {
7
+ const next = new ResourceRegistry();
8
+ for (const module of modules) {
9
+ identifier(module.name);
10
+ if (next.#modules.has(module.name))
11
+ throw new Error(`Duplicate module: ${module.name}`);
12
+ for (const dep of module.dependsOn)
13
+ if (!next.#modules.has(dep))
14
+ throw new Error(`Module ${module.name} must follow dependency ${dep}`);
15
+ next.#modules.set(module.name, module);
16
+ for (const resource of module.resources) {
17
+ if (next.#resources.has(resource.name))
18
+ throw new Error(`Duplicate resource: ${resource.name}`);
19
+ next.#resources.set(resource.name, resource);
20
+ next.#owners.set(resource.name, module.name);
21
+ }
22
+ }
23
+ for (const resource of next.all()) {
24
+ for (const field of Object.values(resource.fields)) {
25
+ if (field.type !== 'belongsTo' && field.type !== 'hasMany')
26
+ continue;
27
+ const target = next.get(field.resource);
28
+ const owner = next.owner(resource.name);
29
+ const targetOwner = next.owner(target.name);
30
+ if (owner !== targetOwner && !next.#modules.get(owner).dependsOn.includes(targetOwner))
31
+ throw new Error(`Undeclared dependency: ${owner} -> ${targetOwner}`);
32
+ if (field.type === 'hasMany' && owner !== targetOwner)
33
+ throw new Error('Inline children must belong to the same module');
34
+ if (field.type === 'hasMany' && !(field.foreignKey in target.fields))
35
+ throw new Error(`Missing child foreign key: ${field.foreignKey}`);
36
+ }
37
+ }
38
+ this.#resources = next.#resources;
39
+ this.#owners = next.#owners;
40
+ this.#modules = next.#modules;
41
+ return this;
42
+ }
43
+ get(name) {
44
+ const resource = this.#resources.get(name);
45
+ if (!resource)
46
+ throw new Error(`Unknown resource: ${name}`);
47
+ return resource;
48
+ }
49
+ all() {
50
+ return [...this.#resources.values()];
51
+ }
52
+ owner(name) {
53
+ this.get(name);
54
+ return this.#owners.get(name);
55
+ }
56
+ modules() {
57
+ return [...this.#modules.values()];
58
+ }
59
+ }
@@ -0,0 +1,80 @@
1
+ import type { Knex } from 'knex';
2
+ import type { LucidModel } from '@adonisjs/lucid/types/model';
3
+ export type Label = {
4
+ ar: string;
5
+ en: string;
6
+ };
7
+ export type Action = 'view' | 'create' | 'update' | 'delete' | 'submit' | 'cancel' | 'amend';
8
+ export type Scalar = string | number | boolean | null;
9
+ export type RecordData = Record<string, unknown>;
10
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
11
+ [key: string]: JsonValue;
12
+ };
13
+ export type SerializedRecord = Record<string, JsonValue>;
14
+ export type Field = {
15
+ label: Label;
16
+ column?: string;
17
+ required?: boolean;
18
+ unique?: boolean;
19
+ sortable?: boolean;
20
+ searchable?: boolean;
21
+ filterable?: boolean;
22
+ permissionLevel?: number;
23
+ sequence?: string;
24
+ } & ({
25
+ type: 'string' | 'text' | 'integer' | 'money' | 'boolean' | 'date' | 'datetime' | 'json' | 'attachment';
26
+ } | {
27
+ type: 'belongsTo';
28
+ resource: string;
29
+ } | {
30
+ type: 'hasMany';
31
+ resource: string;
32
+ foreignKey: string;
33
+ inline?: boolean;
34
+ } | {
35
+ type: 'lookup';
36
+ group: string;
37
+ });
38
+ export type Resource = {
39
+ name: string;
40
+ label: Label;
41
+ model: LucidModel;
42
+ scoped: boolean;
43
+ submittable?: boolean;
44
+ version?: boolean;
45
+ customFields?: boolean;
46
+ fields: Record<string, Field>;
47
+ list: readonly string[];
48
+ form: readonly string[];
49
+ show: readonly string[];
50
+ serialize?: readonly string[];
51
+ hidden?: readonly string[];
52
+ actions: readonly Action[];
53
+ validator: {
54
+ validate(data: unknown): Promise<RecordData>;
55
+ };
56
+ hooks?: {
57
+ beforeSave?: (record: RecordData, context: HookContext) => Promise<void>;
58
+ afterSave?: (record: RecordData, context: HookContext) => Promise<void>;
59
+ };
60
+ };
61
+ export type HookContext = {
62
+ trx: Knex.Transaction;
63
+ userId: number;
64
+ action: Action;
65
+ };
66
+ export type Module = {
67
+ name: string;
68
+ reference?: boolean;
69
+ label: Label;
70
+ dependsOn: readonly string[];
71
+ resources: readonly Resource[];
72
+ };
73
+ export type ResourceInput<F extends Record<string, Field>> = Omit<Resource, 'fields' | 'list' | 'form' | 'show' | 'hidden' | 'serialize'> & {
74
+ fields: F;
75
+ list: readonly (keyof F & string)[];
76
+ form: readonly (keyof F & string)[];
77
+ show: readonly (keyof F & string)[];
78
+ hidden?: readonly (keyof F & string)[];
79
+ serialize?: readonly (keyof F & string)[];
80
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { Field } from './types.js';
2
+ export declare function canonicalDate(value: unknown, type: 'date' | 'datetime'): boolean;
3
+ export declare function canonicalMoney(value: unknown): value is string;
4
+ /** Normalize before CASL evaluates a prospective record, using its stored representation. */
5
+ export declare function fieldValue(field: Field, value: unknown): unknown;
@@ -0,0 +1,87 @@
1
+ export function canonicalDate(value, type) {
2
+ if (typeof value !== 'string')
3
+ return false;
4
+ const normalized = type === 'date' ? `${value}T00:00:00.000Z` : value;
5
+ return (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(normalized) &&
6
+ !normalized.startsWith('0000') &&
7
+ Number.isFinite(Date.parse(normalized)) &&
8
+ new Date(normalized).toISOString() === normalized);
9
+ }
10
+ export function canonicalMoney(value) {
11
+ if (typeof value !== 'string' || !/^(0|-?[1-9]\d*)$/.test(value) || value.length > 20)
12
+ return false;
13
+ const integer = BigInt(value);
14
+ return integer >= -9223372036854775808n && integer <= 9223372036854775807n;
15
+ }
16
+ function isJson(value, ancestors = new Set()) {
17
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
18
+ return true;
19
+ if (typeof value === 'number')
20
+ return Number.isFinite(value);
21
+ if (typeof value !== 'object' || ancestors.has(value))
22
+ return false;
23
+ if (!Array.isArray(value) &&
24
+ Object.getPrototypeOf(value) !== Object.prototype &&
25
+ Object.getPrototypeOf(value) !== null)
26
+ return false;
27
+ // JSON.stringify changes holes into null; never silently alter a submitted value.
28
+ if (Array.isArray(value)) {
29
+ for (let index = 0; index < value.length; index++)
30
+ if (!Object.hasOwn(value, index))
31
+ return false;
32
+ }
33
+ ancestors.add(value);
34
+ const valid = Object.values(value).every((entry) => isJson(entry, ancestors));
35
+ ancestors.delete(value);
36
+ return valid;
37
+ }
38
+ /** Normalize before CASL evaluates a prospective record, using its stored representation. */
39
+ export function fieldValue(field, value) {
40
+ if (value === undefined || value === null)
41
+ return value;
42
+ switch (field.type) {
43
+ case 'money': {
44
+ const amount = typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value;
45
+ if (!canonicalMoney(amount))
46
+ throw new Error('Money requires canonical integer minor units within PostgreSQL bigint range');
47
+ return amount;
48
+ }
49
+ case 'integer':
50
+ case 'belongsTo':
51
+ if (!Number.isInteger(value) ||
52
+ Number(value) < (field.type === 'belongsTo' ? 1 : -2147483648) ||
53
+ Number(value) > 2147483647)
54
+ throw new Error('Expected a PostgreSQL integer');
55
+ return value;
56
+ case 'boolean':
57
+ if (typeof value !== 'boolean')
58
+ throw new Error('Expected a boolean');
59
+ return value;
60
+ case 'string':
61
+ case 'text':
62
+ case 'lookup':
63
+ if (typeof value !== 'string')
64
+ throw new Error('Expected a string');
65
+ return value;
66
+ case 'date':
67
+ case 'datetime': {
68
+ const normalized = field.type === 'datetime' && value instanceof Date && Number.isFinite(value.getTime())
69
+ ? value.toISOString()
70
+ : value;
71
+ if (!canonicalDate(normalized, field.type))
72
+ throw new Error('Expected a valid ISO date or canonical UTC timestamp');
73
+ return normalized;
74
+ }
75
+ case 'json':
76
+ if (!isJson(value))
77
+ throw new Error('Expected a JSON value without cycles or non-JSON values');
78
+ return value;
79
+ case 'attachment':
80
+ // Writes carry the id of an owned upload; reads are hydrated into summaries later.
81
+ if (!Number.isSafeInteger(value) || Number(value) < 1 || Number(value) > 2147483647)
82
+ throw new Error('Expected the identifier of an uploaded attachment');
83
+ return value;
84
+ default:
85
+ return value;
86
+ }
87
+ }
@@ -0,0 +1,14 @@
1
+ import type { Knex } from 'knex';
2
+ export type BackupObject = {
3
+ key: string;
4
+ size: number;
5
+ modifiedAt: string;
6
+ };
7
+ export type BackupStatus = {
8
+ healthy: boolean;
9
+ reason: string;
10
+ checkedAt: string;
11
+ };
12
+ /** Check one complete recent snapshot; object existence is not a restore test. */
13
+ export declare function assessBackup(objects: BackupObject[], now?: number, prefix?: string): BackupStatus;
14
+ export declare function verifyBackup(db: Knex, inspect: () => Promise<BackupObject[]>, now?: number, prefix?: string): Promise<BackupStatus>;
@@ -0,0 +1,59 @@
1
+ import { Settings, notify } from './settings.js';
2
+ /** Check one complete recent snapshot; object existence is not a restore test. */
3
+ export function assessBackup(objects, now = Date.now(), prefix = 'adula') {
4
+ if (!/^adula(?:\/[a-zA-Z0-9_-]+)*$/.test(prefix))
5
+ throw new Error('Invalid backup prefix');
6
+ const root = prefix + '/';
7
+ const snapshots = new Map();
8
+ for (const object of objects) {
9
+ if (!object.key.startsWith(root))
10
+ continue;
11
+ const match = /^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z)\/(database\.dump|uploads\.tar\.gz|SHA256SUMS|COMPLETE)$/.exec(object.key.slice(root.length));
12
+ const age = now - Date.parse(object.modifiedAt);
13
+ if (!match ||
14
+ !Number.isFinite(object.size) ||
15
+ (match[2] === 'COMPLETE' ? object.size < 0 : object.size <= 0) ||
16
+ !Number.isFinite(age) ||
17
+ age < 0 ||
18
+ age > 48 * 3600000)
19
+ continue;
20
+ const files = snapshots.get(match[1]) ?? new Set();
21
+ files.add(match[2]);
22
+ snapshots.set(match[1], files);
23
+ }
24
+ const healthy = [...snapshots.values()].some((files) => files.size === 4);
25
+ return {
26
+ healthy,
27
+ reason: healthy ? 'recent_complete_snapshot' : 'missing_recent_complete_snapshot',
28
+ checkedAt: new Date(now).toISOString(),
29
+ };
30
+ }
31
+ export async function verifyBackup(db, inspect, now = Date.now(), prefix = 'adula') {
32
+ let status;
33
+ try {
34
+ status = assessBackup(await inspect(), now, prefix);
35
+ }
36
+ catch {
37
+ status = {
38
+ healthy: false,
39
+ reason: 'offsite_check_failed',
40
+ checkedAt: new Date(now).toISOString(),
41
+ };
42
+ }
43
+ await db.transaction(async (trx) => {
44
+ await trx.raw('SELECT pg_advisory_xact_lock(?)', [19300210]);
45
+ const settings = new Settings(trx);
46
+ const previous = await settings.get('backup.health');
47
+ await settings.set('backup.health', status);
48
+ if (!status.healthy && (previous?.healthy !== false || previous.reason !== status.reason)) {
49
+ const admins = await trx('user_roles as ur')
50
+ .join('roles as r', 'r.id', 'ur.role_id')
51
+ .where('r.permission_level', '>=', 1)
52
+ .whereNull('ur.org_unit_id')
53
+ .distinct('ur.user_id');
54
+ for (const admin of admins)
55
+ await notify(trx, admin.user_id, 'تعذّر التحقق من النسخة الاحتياطية', 'تحقق من مخزن النسخ الخارجي ووجود نسخة كاملة خلال آخر 48 ساعة.');
56
+ }
57
+ });
58
+ return status;
59
+ }
@@ -0,0 +1,9 @@
1
+ import type { Knex } from 'knex';
2
+ export declare class Settings {
3
+ private db;
4
+ constructor(db: Knex);
5
+ get<T>(key: string, scope?: string, scopeId?: string): Promise<T | undefined>;
6
+ set(key: string, value: unknown, scope?: string, scopeId?: string): Promise<void>;
7
+ }
8
+ export declare function sequence(trx: Knex.Transaction, key: string): Promise<string>;
9
+ export declare function notify(db: Knex, userId: number, title: string, body: string): Promise<void>;
@@ -0,0 +1,23 @@
1
+ export class Settings {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ async get(key, scope = 'system', scopeId = '0') {
7
+ const row = await this.db('settings').where({ key, scope, scope_id: scopeId }).first('value');
8
+ return row?.value;
9
+ }
10
+ async set(key, value, scope = 'system', scopeId = '0') {
11
+ await this.db('settings')
12
+ .insert({ key, scope, scope_id: scopeId, value: JSON.stringify(value) })
13
+ .onConflict(['key', 'scope', 'scope_id'])
14
+ .merge(['value']);
15
+ }
16
+ }
17
+ export async function sequence(trx, key) {
18
+ const result = await trx.raw('INSERT INTO sequences (key,value) VALUES (?,1) ON CONFLICT (key) DO UPDATE SET value=sequences.value+1 RETURNING value', [key]);
19
+ return `${key}-${String(result.rows[0].value).padStart(6, '0')}`;
20
+ }
21
+ export async function notify(db, userId, title, body) {
22
+ await db('notifications').insert({ user_id: userId, title, body });
23
+ }
@@ -0,0 +1 @@
1
+ export declare const KIT_VERSION: string;
@@ -0,0 +1,2 @@
1
+ import { createRequire } from 'node:module';
2
+ export const KIT_VERSION = createRequire(import.meta.url)('@adula/kit/package.json').version;
@@ -0,0 +1,6 @@
1
+ # The stubs directory
2
+
3
+ The `stubs` directory stores all the stubs needed by your package. It could be config files you will publish during the initial setup or stubs you want to use within the scaffolding commands.
4
+
5
+ - Inside the `package.json` file, we have defined a `copy:templates` script that copies the `stubs` folder to the `build` folder.
6
+ - Ensure the `build/stubs` are always published to npm via the `files` array inside the `package.json` file.
@@ -0,0 +1,8 @@
1
+ {{{ exports({ to: app.makePath('app/controllers/resources_controller.ts') }) }}}
2
+ import { createResourceController } from '@adula/kit'
3
+ import { kit } from '#services/kit'
4
+
5
+ export default createResourceController(async (ctx) => {
6
+ const runtime = kit()
7
+ return { ...runtime, actor: await runtime.actors.load(ctx.auth.getUserOrFail().id) }
8
+ })
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Path to the root directory where the stubs are stored. We use
3
+ * this path within commands and the configure hook
4
+ */
5
+ export declare const stubsRoot: string;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Path to the root directory where the stubs are stored. We use
3
+ * this path within commands and the configure hook
4
+ */
5
+ export const stubsRoot = import.meta.dirname;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Path to the root directory where the stubs are stored. We use
3
+ * this path within commands and the configure hook
4
+ */
5
+ export const stubsRoot = import.meta.dirname
@@ -0,0 +1,6 @@
1
+ {{{ exports({ to: app.makePath('start/modules.ts') }) }}}
2
+ import { ResourceRegistry } from '@adula/kit'
3
+ import type { Module } from '@adula/kit'
4
+ // adula:imports
5
+ export const modules: Module[] = [/* adula:modules */]
6
+ export const registry = new ResourceRegistry().register(modules)
@@ -0,0 +1 @@
1
+ {{{ exports({ to: app.makePath('tests/helpers/resource_contract.ts') }) }}}