@loom-framework/core 0.1.0-alpha.8 → 0.1.0-alpha.80
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/dist/adapter-base.d.ts +29 -0
- package/dist/adapter-base.d.ts.map +1 -0
- package/dist/adapter-base.js +62 -0
- package/dist/adapter-base.js.map +1 -0
- package/dist/adapter-factory.d.ts +8 -0
- package/dist/adapter-factory.d.ts.map +1 -0
- package/dist/adapter-factory.js +25 -0
- package/dist/adapter-factory.js.map +1 -0
- package/dist/adapter-filesystem.d.ts +6 -11
- package/dist/adapter-filesystem.d.ts.map +1 -1
- package/dist/adapter-filesystem.js +56 -41
- package/dist/adapter-filesystem.js.map +1 -1
- package/dist/adapter-sqlite.d.ts +6 -23
- package/dist/adapter-sqlite.d.ts.map +1 -1
- package/dist/adapter-sqlite.js +65 -50
- package/dist/adapter-sqlite.js.map +1 -1
- package/dist/backend/ai/button-resolver.d.ts +18 -0
- package/dist/backend/ai/button-resolver.d.ts.map +1 -0
- package/dist/backend/ai/button-resolver.js +58 -0
- package/dist/backend/ai/button-resolver.js.map +1 -0
- package/dist/backend/ai/engine.d.ts +52 -0
- package/dist/backend/ai/engine.d.ts.map +1 -0
- package/dist/backend/ai/engine.js +186 -0
- package/dist/backend/ai/engine.js.map +1 -0
- package/dist/backend/ai/index.d.ts +11 -0
- package/dist/backend/ai/index.d.ts.map +1 -0
- package/dist/backend/ai/index.js +8 -0
- package/dist/backend/ai/index.js.map +1 -0
- package/dist/backend/ai/output-parser.d.ts +29 -0
- package/dist/backend/ai/output-parser.d.ts.map +1 -0
- package/dist/backend/ai/output-parser.js +247 -0
- package/dist/backend/ai/output-parser.js.map +1 -0
- package/dist/backend/ai/session-manager.d.ts +103 -0
- package/dist/backend/ai/session-manager.d.ts.map +1 -0
- package/dist/backend/ai/session-manager.js +298 -0
- package/dist/backend/ai/session-manager.js.map +1 -0
- package/dist/backend/index.d.ts +61 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +161 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/observe/index.d.ts +6 -0
- package/dist/backend/observe/index.d.ts.map +1 -0
- package/dist/backend/observe/index.js +5 -0
- package/dist/backend/observe/index.js.map +1 -0
- package/dist/backend/observe/logger.d.ts +28 -0
- package/dist/backend/observe/logger.d.ts.map +1 -0
- package/dist/backend/observe/logger.js +80 -0
- package/dist/backend/observe/logger.js.map +1 -0
- package/dist/backend/observe/types.d.ts +26 -0
- package/dist/backend/observe/types.d.ts.map +1 -0
- package/dist/backend/observe/types.js +7 -0
- package/dist/backend/observe/types.js.map +1 -0
- package/dist/backend/routes/chat.d.ts +31 -0
- package/dist/backend/routes/chat.d.ts.map +1 -0
- package/dist/backend/routes/chat.js +426 -0
- package/dist/backend/routes/chat.js.map +1 -0
- package/dist/backend/routes/data.d.ts +13 -0
- package/dist/backend/routes/data.d.ts.map +1 -0
- package/dist/backend/routes/data.js +134 -0
- package/dist/backend/routes/data.js.map +1 -0
- package/dist/backend/routes/health.d.ts +7 -0
- package/dist/backend/routes/health.d.ts.map +1 -0
- package/dist/backend/routes/health.js +15 -0
- package/dist/backend/routes/health.js.map +1 -0
- package/dist/backend/routes/index.d.ts +11 -0
- package/dist/backend/routes/index.d.ts.map +1 -0
- package/dist/backend/routes/index.js +9 -0
- package/dist/backend/routes/index.js.map +1 -0
- package/dist/backend/routes/skills.d.ts +16 -0
- package/dist/backend/routes/skills.d.ts.map +1 -0
- package/dist/backend/routes/skills.js +590 -0
- package/dist/backend/routes/skills.js.map +1 -0
- package/dist/backend/routes/upload.d.ts +24 -0
- package/dist/backend/routes/upload.d.ts.map +1 -0
- package/dist/backend/routes/upload.js +67 -0
- package/dist/backend/routes/upload.js.map +1 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +12 -0
- package/dist/bin.js.map +1 -0
- package/dist/capability-generator.d.ts +21 -6
- package/dist/capability-generator.d.ts.map +1 -1
- package/dist/capability-generator.js +88 -261
- package/dist/capability-generator.js.map +1 -1
- package/dist/cli/commands/build.d.ts +11 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +170 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/data.d.ts +12 -0
- package/dist/cli/commands/data.d.ts.map +1 -0
- package/dist/cli/commands/data.js +158 -0
- package/dist/cli/commands/data.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +9 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +114 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/generate-capabilities.d.ts +8 -0
- package/dist/cli/commands/generate-capabilities.d.ts.map +1 -0
- package/dist/cli/commands/generate-capabilities.js +40 -0
- package/dist/cli/commands/generate-capabilities.js.map +1 -0
- package/dist/cli/commands/generate-cli-command.d.ts +8 -0
- package/dist/cli/commands/generate-cli-command.d.ts.map +1 -0
- package/dist/cli/commands/generate-cli-command.js +64 -0
- package/dist/cli/commands/generate-cli-command.js.map +1 -0
- package/dist/cli/commands/generate-page.d.ts +9 -0
- package/dist/cli/commands/generate-page.d.ts.map +1 -0
- package/dist/cli/commands/generate-page.js +641 -0
- package/dist/cli/commands/generate-page.js.map +1 -0
- package/dist/cli/commands/generate-skill.d.ts +8 -0
- package/dist/cli/commands/generate-skill.d.ts.map +1 -0
- package/dist/cli/commands/generate-skill.js +75 -0
- package/dist/cli/commands/generate-skill.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +6 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +17 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +539 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/observe.d.ts +9 -0
- package/dist/cli/commands/observe.d.ts.map +1 -0
- package/dist/cli/commands/observe.js +142 -0
- package/dist/cli/commands/observe.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +9 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +186 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/helpers/duration.d.ts +5 -0
- package/dist/cli/helpers/duration.d.ts.map +1 -0
- package/dist/cli/helpers/duration.js +19 -0
- package/dist/cli/helpers/duration.js.map +1 -0
- package/dist/cli/helpers/field-template.d.ts +10 -0
- package/dist/cli/helpers/field-template.d.ts.map +1 -0
- package/dist/cli/helpers/field-template.js +100 -0
- package/dist/cli/helpers/field-template.js.map +1 -0
- package/dist/cli/helpers/naming.d.ts +12 -0
- package/dist/cli/helpers/naming.d.ts.map +1 -0
- package/dist/cli/helpers/naming.js +25 -0
- package/dist/cli/helpers/naming.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +33 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils.d.ts +10 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +31 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/config.d.ts +8 -40
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -8
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/server-bin.d.ts +12 -0
- package/dist/server-bin.d.ts.map +1 -0
- package/dist/server-bin.js +75 -0
- package/dist/server-bin.js.map +1 -0
- package/dist/types.d.ts +33 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +25 -10
- package/templates/app-skill/SKILL.md +27 -0
- package/templates/app-skill/references/data-semantics.md +44 -0
- package/templates/app-skill/references/models.md +31 -0
- package/templates/loom-skill/SKILL.md +140 -0
- package/templates/loom-skill/references/README.md +128 -0
- package/templates/loom-skill/references/data-model.md +78 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAdapter - Shared logic for DataAdapter implementations
|
|
3
|
+
*
|
|
4
|
+
* Extracts common validation, ID generation, and schema management
|
|
5
|
+
* used by both FileSystemAdapter and SQLiteAdapter.
|
|
6
|
+
*/
|
|
7
|
+
import type { DataAdapter, DataRecord, ModelSchema, LoomConfig } from './types.js';
|
|
8
|
+
export declare abstract class BaseAdapter implements DataAdapter {
|
|
9
|
+
abstract readonly name: string;
|
|
10
|
+
protected schemas: Map<string, ModelSchema>;
|
|
11
|
+
protected config: LoomConfig;
|
|
12
|
+
constructor(config: LoomConfig);
|
|
13
|
+
abstract initialize(): Promise<void>;
|
|
14
|
+
abstract healthCheck(): Promise<boolean>;
|
|
15
|
+
abstract read(model: string, options?: import('./types.js').ReadOptions): Promise<DataRecord | DataRecord[] | null>;
|
|
16
|
+
abstract write(model: string, data: DataRecord): Promise<DataRecord>;
|
|
17
|
+
abstract update(model: string, id: string, data: DataRecord): Promise<DataRecord>;
|
|
18
|
+
abstract delete(model: string, id: string): Promise<void>;
|
|
19
|
+
getSchema(model: string): Promise<ModelSchema | undefined>;
|
|
20
|
+
listModels(): Promise<string[]>;
|
|
21
|
+
protected requireModel(model: string): void;
|
|
22
|
+
protected generateId(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Validate required fields and apply defaults on a record.
|
|
25
|
+
* Returns the validated record with defaults applied.
|
|
26
|
+
*/
|
|
27
|
+
protected validateAndApplyDefaults(model: string, record: DataRecord, skipIdValidation?: boolean): DataRecord;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=adapter-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-base.d.ts","sourceRoot":"","sources":["../src/adapter-base.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnF,8BAAsB,WAAY,YAAW,WAAW;IACtD,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAa;IACxD,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC;gBAEjB,MAAM,EAAE,UAAU;IAO9B,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IACxC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC;IACnH,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACpE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACjF,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnD,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAI1D,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAMrC,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ3C,SAAS,CAAC,UAAU,IAAI,MAAM;IAM9B;;;OAGG;IACH,SAAS,CAAC,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,UAAQ,GAAG,UAAU;CA2B5G"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAdapter - Shared logic for DataAdapter implementations
|
|
3
|
+
*
|
|
4
|
+
* Extracts common validation, ID generation, and schema management
|
|
5
|
+
* used by both FileSystemAdapter and SQLiteAdapter.
|
|
6
|
+
*/
|
|
7
|
+
export class BaseAdapter {
|
|
8
|
+
schemas = new Map();
|
|
9
|
+
config;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
for (const model of config.data.models) {
|
|
13
|
+
this.schemas.set(model.name, model);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async getSchema(model) {
|
|
17
|
+
return this.schemas.get(model);
|
|
18
|
+
}
|
|
19
|
+
async listModels() {
|
|
20
|
+
return Array.from(this.schemas.keys());
|
|
21
|
+
}
|
|
22
|
+
// ── Shared Helpers ──
|
|
23
|
+
requireModel(model) {
|
|
24
|
+
if (!this.schemas.has(model)) {
|
|
25
|
+
throw new Error(`Unknown model: ${model}. Available: ${Array.from(this.schemas.keys()).join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
generateId() {
|
|
29
|
+
const timestamp = Date.now().toString(36);
|
|
30
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
31
|
+
return `rec_${timestamp}_${random}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate required fields and apply defaults on a record.
|
|
35
|
+
* Returns the validated record with defaults applied.
|
|
36
|
+
*/
|
|
37
|
+
validateAndApplyDefaults(model, record, skipIdValidation = false) {
|
|
38
|
+
const schema = this.schemas.get(model);
|
|
39
|
+
// Apply default values for missing fields
|
|
40
|
+
for (const field of schema.fields) {
|
|
41
|
+
if (record[field.name] === undefined && field.default !== undefined) {
|
|
42
|
+
record[field.name] = field.default;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Validate required fields
|
|
46
|
+
for (const field of schema.fields) {
|
|
47
|
+
if (skipIdValidation && field.name === 'id')
|
|
48
|
+
continue;
|
|
49
|
+
if (field.required && record[field.name] === undefined) {
|
|
50
|
+
throw new Error(`Missing required field: ${field.name}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Validate enum constraints
|
|
54
|
+
for (const field of schema.fields) {
|
|
55
|
+
if (field.enum && record[field.name] !== undefined && !field.enum.includes(record[field.name])) {
|
|
56
|
+
throw new Error(`Invalid value for field '${field.name}': ${record[field.name]}. Allowed values: ${field.enum.join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return record;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=adapter-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-base.js","sourceRoot":"","sources":["../src/adapter-base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates the appropriate DataAdapter based on LoomConfig.data.defaultAdapter.
|
|
5
|
+
*/
|
|
6
|
+
import type { LoomConfig, DataAdapter } from './types.js';
|
|
7
|
+
export declare function createAdapter(config: LoomConfig, projectRoot: string): DataAdapter;
|
|
8
|
+
//# sourceMappingURL=adapter-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-factory.d.ts","sourceRoot":"","sources":["../src/adapter-factory.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE1D,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,CAgBlF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates the appropriate DataAdapter based on LoomConfig.data.defaultAdapter.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { FileSystemAdapter } from './adapter-filesystem.js';
|
|
8
|
+
import { SQLiteAdapter } from './adapter-sqlite.js';
|
|
9
|
+
export function createAdapter(config, projectRoot) {
|
|
10
|
+
const adapterType = config.data.defaultAdapter || 'filesystem';
|
|
11
|
+
switch (adapterType) {
|
|
12
|
+
case 'sqlite':
|
|
13
|
+
return new SQLiteAdapter({
|
|
14
|
+
dbPath: path.join(projectRoot, 'data', config.data.sqlite?.filename ?? 'loom.db'),
|
|
15
|
+
config,
|
|
16
|
+
});
|
|
17
|
+
case 'filesystem':
|
|
18
|
+
default:
|
|
19
|
+
return new FileSystemAdapter({
|
|
20
|
+
dataDir: path.join(projectRoot, 'data'),
|
|
21
|
+
config,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=adapter-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-factory.js","sourceRoot":"","sources":["../src/adapter-factory.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,MAAM,UAAU,aAAa,CAAC,MAAkB,EAAE,WAAmB;IACnE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,YAAY,CAAC;IAE/D,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,IAAI,aAAa,CAAC;gBACvB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,SAAS,CAAC;gBACjF,MAAM;aACP,CAAC,CAAC;QACL,KAAK,YAAY,CAAC;QAClB;YACE,OAAO,IAAI,iBAAiB,CAAC;gBAC3B,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;gBACvC,MAAM;aACP,CAAC,CAAC;IACP,CAAC;AACH,CAAC"}
|
|
@@ -4,29 +4,24 @@
|
|
|
4
4
|
* Stores data as JSON files, organized by model.
|
|
5
5
|
* Default adapter for development and small deployments.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { DataRecord, ReadOptions, LoomConfig } from './types.js';
|
|
8
|
+
import { BaseAdapter } from './adapter-base.js';
|
|
8
9
|
export interface FileSystemAdapterOptions {
|
|
9
10
|
dataDir: string;
|
|
10
11
|
config: LoomConfig;
|
|
11
12
|
}
|
|
12
|
-
export declare class FileSystemAdapter
|
|
13
|
+
export declare class FileSystemAdapter extends BaseAdapter {
|
|
13
14
|
readonly name = "filesystem";
|
|
14
15
|
private dataDir;
|
|
15
|
-
private config;
|
|
16
|
-
private schemas;
|
|
17
16
|
constructor(options: FileSystemAdapterOptions);
|
|
18
17
|
initialize(): Promise<void>;
|
|
19
18
|
healthCheck(): Promise<boolean>;
|
|
20
|
-
read(model: string, options?: ReadOptions): Promise<
|
|
21
|
-
write(model: string, data:
|
|
22
|
-
update(model: string, id: string, data:
|
|
19
|
+
read(model: string, options?: ReadOptions): Promise<DataRecord | DataRecord[] | null>;
|
|
20
|
+
write(model: string, data: DataRecord): Promise<DataRecord>;
|
|
21
|
+
update(model: string, id: string, data: DataRecord): Promise<DataRecord>;
|
|
23
22
|
delete(model: string, id: string): Promise<void>;
|
|
24
|
-
getSchema(model: string): Promise<ModelSchema | undefined>;
|
|
25
|
-
listModels(): Promise<string[]>;
|
|
26
|
-
private requireModel;
|
|
27
23
|
private getModelDir;
|
|
28
24
|
private getRecordPath;
|
|
29
25
|
private matchesFilter;
|
|
30
|
-
private generateId;
|
|
31
26
|
}
|
|
32
27
|
//# sourceMappingURL=adapter-filesystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-filesystem.d.ts","sourceRoot":"","sources":["../src/adapter-filesystem.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"adapter-filesystem.d.ts","sourceRoot":"","sources":["../src/adapter-filesystem.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,wBAAwB;IAKvC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAS/B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC;IA6DrF,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAc3D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAmBxE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAatD,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;CAoBtB"}
|
|
@@ -6,18 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { promises as fs } from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
|
|
9
|
+
import { BaseAdapter } from './adapter-base.js';
|
|
10
|
+
export class FileSystemAdapter extends BaseAdapter {
|
|
10
11
|
name = 'filesystem';
|
|
11
12
|
dataDir;
|
|
12
|
-
config;
|
|
13
|
-
schemas = new Map();
|
|
14
13
|
constructor(options) {
|
|
14
|
+
super(options.config);
|
|
15
15
|
this.dataDir = options.dataDir;
|
|
16
|
-
this.config = options.config;
|
|
17
|
-
// Index model schemas
|
|
18
|
-
for (const model of options.config.data.models) {
|
|
19
|
-
this.schemas.set(model.name, model);
|
|
20
|
-
}
|
|
21
16
|
}
|
|
22
17
|
async initialize() {
|
|
23
18
|
await fs.mkdir(this.dataDir, { recursive: true });
|
|
@@ -57,28 +52,49 @@ export class FileSystemAdapter {
|
|
|
57
52
|
if (!file.endsWith('.json'))
|
|
58
53
|
continue;
|
|
59
54
|
const content = await fs.readFile(path.join(modelDir, file), 'utf-8');
|
|
60
|
-
const
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
const parsed = JSON.parse(content);
|
|
56
|
+
// Handle both single records and arrays (e.g. init.json seed data)
|
|
57
|
+
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
58
|
+
for (const record of items) {
|
|
59
|
+
// Apply filter if provided
|
|
60
|
+
if (options?.filter && !this.matchesFilter(record, options.filter)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
records.push(record);
|
|
64
64
|
}
|
|
65
|
-
records.push(record);
|
|
66
65
|
}
|
|
67
|
-
|
|
66
|
+
// Apply sort if provided
|
|
67
|
+
if (options?.sort) {
|
|
68
|
+
const { field, order } = options.sort;
|
|
69
|
+
records.sort((a, b) => {
|
|
70
|
+
const va = a[field];
|
|
71
|
+
const vb = b[field];
|
|
72
|
+
if (va == null && vb == null)
|
|
73
|
+
return 0;
|
|
74
|
+
if (va == null)
|
|
75
|
+
return 1;
|
|
76
|
+
if (vb == null)
|
|
77
|
+
return -1;
|
|
78
|
+
let cmp;
|
|
79
|
+
if (typeof va === 'number' && typeof vb === 'number') {
|
|
80
|
+
cmp = va - vb;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
cmp = String(va).localeCompare(String(vb));
|
|
84
|
+
}
|
|
85
|
+
return order === 'desc' ? -cmp : cmp;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Apply offset and limit
|
|
89
|
+
const start = options?.offset || 0;
|
|
90
|
+
const end = options?.limit ? start + options.limit : undefined;
|
|
91
|
+
return records.slice(start, end);
|
|
68
92
|
}
|
|
69
93
|
async write(model, data) {
|
|
70
94
|
this.requireModel(model);
|
|
71
|
-
const schema = this.schemas.get(model);
|
|
72
95
|
const id = data.id || this.generateId();
|
|
73
|
-
// Build the full record first (with auto-generated id)
|
|
74
96
|
const record = { id, ...data };
|
|
75
|
-
|
|
76
|
-
// so auto-generated fields like id are not falsely flagged as missing)
|
|
77
|
-
for (const field of schema.fields) {
|
|
78
|
-
if (field.required && record[field.name] === undefined) {
|
|
79
|
-
throw new Error(`Missing required field: ${field.name}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
97
|
+
this.validateAndApplyDefaults(model, record);
|
|
82
98
|
const filePath = this.getRecordPath(model, id);
|
|
83
99
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
84
100
|
await fs.writeFile(filePath, JSON.stringify(record, null, 2), 'utf-8');
|
|
@@ -109,18 +125,7 @@ export class FileSystemAdapter {
|
|
|
109
125
|
throw new Error(`Record not found: ${model}/${id}`);
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
|
-
async getSchema(model) {
|
|
113
|
-
return this.schemas.get(model);
|
|
114
|
-
}
|
|
115
|
-
async listModels() {
|
|
116
|
-
return Array.from(this.schemas.keys());
|
|
117
|
-
}
|
|
118
128
|
// ── Private Helpers ──
|
|
119
|
-
requireModel(model) {
|
|
120
|
-
if (!this.schemas.has(model)) {
|
|
121
|
-
throw new Error(`Unknown model: ${model}. Available: ${Array.from(this.schemas.keys()).join(', ')}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
129
|
getModelDir(model) {
|
|
125
130
|
const schema = this.schemas.get(model);
|
|
126
131
|
const adapterConfig = schema.adapters?.filesystem;
|
|
@@ -131,13 +136,23 @@ export class FileSystemAdapter {
|
|
|
131
136
|
return path.join(this.getModelDir(model), `${id}.json`);
|
|
132
137
|
}
|
|
133
138
|
matchesFilter(record, filter) {
|
|
134
|
-
return Object.entries(filter).every(([key, value]) =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
140
|
+
const recordVal = record[key];
|
|
141
|
+
// Range filter: { __range: [start, end] }
|
|
142
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && '__range' in value) {
|
|
143
|
+
const range = value.__range;
|
|
144
|
+
if (!range?.[0] || !range?.[1])
|
|
145
|
+
return true;
|
|
146
|
+
if (recordVal == null)
|
|
147
|
+
return false;
|
|
148
|
+
return recordVal >= range[0] && recordVal <= range[1];
|
|
149
|
+
}
|
|
150
|
+
// Array filter: match any value in the array
|
|
151
|
+
if (Array.isArray(value)) {
|
|
152
|
+
return value.includes(recordVal);
|
|
153
|
+
}
|
|
154
|
+
return recordVal === value;
|
|
155
|
+
});
|
|
141
156
|
}
|
|
142
157
|
}
|
|
143
158
|
//# sourceMappingURL=adapter-filesystem.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-filesystem.js","sourceRoot":"","sources":["../src/adapter-filesystem.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter-filesystem.js","sourceRoot":"","sources":["../src/adapter-filesystem.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter-sqlite.d.ts
CHANGED
|
@@ -4,48 +4,31 @@
|
|
|
4
4
|
* Stores data in a SQLite database using better-sqlite3.
|
|
5
5
|
* Suitable for medium-scale and embedded deployments.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { DataRecord, ReadOptions, LoomConfig } from './types.js';
|
|
8
|
+
import { BaseAdapter } from './adapter-base.js';
|
|
8
9
|
export interface SQLiteAdapterOptions {
|
|
9
10
|
/** Path to the SQLite database file */
|
|
10
11
|
dbPath: string;
|
|
11
12
|
config: LoomConfig;
|
|
12
13
|
}
|
|
13
|
-
export declare class SQLiteAdapter
|
|
14
|
+
export declare class SQLiteAdapter extends BaseAdapter {
|
|
14
15
|
readonly name = "sqlite";
|
|
15
16
|
private dbPath;
|
|
16
|
-
private config;
|
|
17
17
|
private db;
|
|
18
|
-
private schemas;
|
|
19
18
|
constructor(options: SQLiteAdapterOptions);
|
|
20
19
|
initialize(): Promise<void>;
|
|
21
20
|
healthCheck(): Promise<boolean>;
|
|
22
|
-
read(model: string, options?: ReadOptions): Promise<
|
|
23
|
-
write(model: string, data:
|
|
24
|
-
update(model: string, id: string, data:
|
|
21
|
+
read(model: string, options?: ReadOptions): Promise<DataRecord | DataRecord[] | null>;
|
|
22
|
+
write(model: string, data: DataRecord): Promise<DataRecord>;
|
|
23
|
+
update(model: string, id: string, data: DataRecord): Promise<DataRecord>;
|
|
25
24
|
delete(model: string, id: string): Promise<void>;
|
|
26
|
-
getSchema(model: string): Promise<ModelSchema | undefined>;
|
|
27
|
-
listModels(): Promise<string[]>;
|
|
28
25
|
/**
|
|
29
26
|
* Close the database connection
|
|
30
27
|
*/
|
|
31
28
|
close(): void;
|
|
32
|
-
private requireModel;
|
|
33
|
-
/**
|
|
34
|
-
* Create a table for a model based on its schema
|
|
35
|
-
*/
|
|
36
29
|
private createTable;
|
|
37
|
-
/**
|
|
38
|
-
* Map Loom field types to SQLite types
|
|
39
|
-
*/
|
|
40
30
|
private mapFieldType;
|
|
41
|
-
/**
|
|
42
|
-
* Serialize a value for SQLite storage
|
|
43
|
-
*/
|
|
44
31
|
private serializeValue;
|
|
45
|
-
/**
|
|
46
|
-
* Deserialize a row from SQLite storage
|
|
47
|
-
*/
|
|
48
32
|
private deserializeRow;
|
|
49
|
-
private generateId;
|
|
50
33
|
}
|
|
51
34
|
//# sourceMappingURL=adapter-sqlite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-sqlite.d.ts","sourceRoot":"","sources":["../src/adapter-sqlite.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"adapter-sqlite.d.ts","sourceRoot":"","sources":["../src/adapter-sqlite.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;CACpB;AAUD,qBAAa,aAAc,SAAQ,WAAW;IAC5C,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAqB;gBAEnB,OAAO,EAAE,oBAAoB;IAKnC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC3B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAS/B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC;IA4DrF,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAmB3D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAoBxE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtD;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,cAAc;CAyBvB"}
|
package/dist/adapter-sqlite.js
CHANGED
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs';
|
|
10
|
-
|
|
10
|
+
import { BaseAdapter } from './adapter-base.js';
|
|
11
|
+
const VALID_IDENTIFIER = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
12
|
+
function validateIdentifier(name, label) {
|
|
13
|
+
if (!VALID_IDENTIFIER.test(name)) {
|
|
14
|
+
throw new Error(`Invalid ${label}: "${name}". Must match /^[a-zA-Z][a-zA-Z0-9_]*$/`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class SQLiteAdapter extends BaseAdapter {
|
|
11
18
|
name = 'sqlite';
|
|
12
19
|
dbPath;
|
|
13
|
-
config;
|
|
14
20
|
db;
|
|
15
|
-
schemas = new Map();
|
|
16
21
|
constructor(options) {
|
|
22
|
+
super(options.config);
|
|
17
23
|
this.dbPath = options.dbPath;
|
|
18
|
-
this.config = options.config;
|
|
19
|
-
for (const model of options.config.data.models) {
|
|
20
|
-
this.schemas.set(model.name, model);
|
|
21
|
-
}
|
|
22
24
|
}
|
|
23
25
|
async initialize() {
|
|
24
26
|
// Ensure directory exists
|
|
@@ -26,9 +28,24 @@ export class SQLiteAdapter {
|
|
|
26
28
|
if (!fs.existsSync(dir)) {
|
|
27
29
|
fs.mkdirSync(dir, { recursive: true });
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
try {
|
|
32
|
+
this.db = new Database(this.dbPath);
|
|
33
|
+
this.db.pragma('journal_mode = WAL');
|
|
34
|
+
this.db.pragma('foreign_keys = ON');
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err?.message ?? String(err);
|
|
38
|
+
if (msg.includes('Could not locate the bindings file') || msg.includes('better_sqlite3.node')) {
|
|
39
|
+
throw new Error('better-sqlite3 native module failed to load.\n' +
|
|
40
|
+
'This usually means the native binary was not compiled for your Node.js version.\n\n' +
|
|
41
|
+
'Fix options:\n' +
|
|
42
|
+
' 1. Rebuild from source: cd node_modules/better-sqlite3 && npx node-gyp rebuild\n' +
|
|
43
|
+
' 2. Switch adapter: change defaultAdapter to "filesystem" in loom.config.ts\n' +
|
|
44
|
+
` 3. Check Node.js: better-sqlite3 may not support Node ${process.version} yet\n\n` +
|
|
45
|
+
`Original error: ${msg}`);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
32
49
|
// Auto-create tables from config
|
|
33
50
|
for (const model of this.config.data.models) {
|
|
34
51
|
this.createTable(model);
|
|
@@ -45,6 +62,7 @@ export class SQLiteAdapter {
|
|
|
45
62
|
}
|
|
46
63
|
async read(model, options) {
|
|
47
64
|
this.requireModel(model);
|
|
65
|
+
validateIdentifier(model, 'model name');
|
|
48
66
|
// Read by ID
|
|
49
67
|
if (options?.id) {
|
|
50
68
|
const row = this.db.prepare(`SELECT * FROM "${model}" WHERE id = ?`).get(options.id);
|
|
@@ -56,31 +74,51 @@ export class SQLiteAdapter {
|
|
|
56
74
|
if (options?.filter && Object.keys(options.filter).length > 0) {
|
|
57
75
|
const filter = options.filter;
|
|
58
76
|
const conditions = Object.entries(filter).map(([key, value]) => {
|
|
77
|
+
validateIdentifier(key, 'filter field name');
|
|
78
|
+
// Range filter: { __range: [start, end] }
|
|
79
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && '__range' in value) {
|
|
80
|
+
const range = value.__range;
|
|
81
|
+
if (range?.[0] && range?.[1]) {
|
|
82
|
+
params.push(range[0], range[1]);
|
|
83
|
+
return `"${key}" BETWEEN ? AND ?`;
|
|
84
|
+
}
|
|
85
|
+
return '1=1';
|
|
86
|
+
}
|
|
87
|
+
// Array filter: match any value in the array
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
const placeholders = value.map(() => '?').join(', ');
|
|
90
|
+
params.push(...value);
|
|
91
|
+
return `"${key}" IN (${placeholders})`;
|
|
92
|
+
}
|
|
93
|
+
// Equal match
|
|
59
94
|
params.push(value);
|
|
60
95
|
return `"${key}" = ?`;
|
|
61
96
|
});
|
|
62
97
|
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
63
98
|
}
|
|
64
|
-
if (options?.
|
|
65
|
-
|
|
66
|
-
|
|
99
|
+
if (options?.sort) {
|
|
100
|
+
validateIdentifier(options.sort.field, 'sort field name');
|
|
101
|
+
sql += ` ORDER BY "${options.sort.field}" ${options.sort.order === 'desc' ? 'DESC' : 'ASC'}`;
|
|
102
|
+
}
|
|
103
|
+
if (options?.limit || options?.offset) {
|
|
104
|
+
if (options?.limit) {
|
|
105
|
+
sql += ` LIMIT ?`;
|
|
106
|
+
params.push(options.limit);
|
|
107
|
+
}
|
|
108
|
+
if (options?.offset) {
|
|
109
|
+
sql += ` OFFSET ?`;
|
|
110
|
+
params.push(options.offset);
|
|
111
|
+
}
|
|
67
112
|
}
|
|
68
113
|
const rows = this.db.prepare(sql).all(...params);
|
|
69
114
|
return rows.map((row) => this.deserializeRow(row, model));
|
|
70
115
|
}
|
|
71
116
|
async write(model, data) {
|
|
72
117
|
this.requireModel(model);
|
|
73
|
-
|
|
118
|
+
validateIdentifier(model, 'model name');
|
|
74
119
|
const id = data.id || this.generateId();
|
|
75
|
-
// Validate required fields (skip id — auto-generated if not provided)
|
|
76
|
-
for (const field of schema.fields) {
|
|
77
|
-
if (field.name === 'id')
|
|
78
|
-
continue;
|
|
79
|
-
if (field.required && data[field.name] === undefined) {
|
|
80
|
-
throw new Error(`Missing required field: ${field.name}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
120
|
const record = { id, ...data };
|
|
121
|
+
this.validateAndApplyDefaults(model, record, true);
|
|
84
122
|
// Insert into table
|
|
85
123
|
const columns = Object.keys(record);
|
|
86
124
|
const values = columns.map((col) => this.serializeValue(record[col], model, col));
|
|
@@ -91,6 +129,7 @@ export class SQLiteAdapter {
|
|
|
91
129
|
}
|
|
92
130
|
async update(model, id, data) {
|
|
93
131
|
this.requireModel(model);
|
|
132
|
+
validateIdentifier(model, 'model name');
|
|
94
133
|
// Check record exists
|
|
95
134
|
const existing = this.db.prepare(`SELECT * FROM "${model}" WHERE id = ?`).get(id);
|
|
96
135
|
if (!existing) {
|
|
@@ -105,17 +144,12 @@ export class SQLiteAdapter {
|
|
|
105
144
|
}
|
|
106
145
|
async delete(model, id) {
|
|
107
146
|
this.requireModel(model);
|
|
147
|
+
validateIdentifier(model, 'model name');
|
|
108
148
|
const result = this.db.prepare(`DELETE FROM "${model}" WHERE id = ?`).run(id);
|
|
109
149
|
if (result.changes === 0) {
|
|
110
150
|
throw new Error(`Record not found: ${model}/${id}`);
|
|
111
151
|
}
|
|
112
152
|
}
|
|
113
|
-
async getSchema(model) {
|
|
114
|
-
return this.schemas.get(model);
|
|
115
|
-
}
|
|
116
|
-
async listModels() {
|
|
117
|
-
return Array.from(this.schemas.keys());
|
|
118
|
-
}
|
|
119
153
|
/**
|
|
120
154
|
* Close the database connection
|
|
121
155
|
*/
|
|
@@ -123,19 +157,12 @@ export class SQLiteAdapter {
|
|
|
123
157
|
this.db?.close();
|
|
124
158
|
}
|
|
125
159
|
// ── Private Helpers ──
|
|
126
|
-
requireModel(model) {
|
|
127
|
-
if (!this.schemas.has(model)) {
|
|
128
|
-
throw new Error(`Unknown model: ${model}. Available: ${Array.from(this.schemas.keys()).join(', ')}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Create a table for a model based on its schema
|
|
133
|
-
*/
|
|
134
160
|
createTable(model) {
|
|
135
161
|
const columns = ['id TEXT PRIMARY KEY'];
|
|
136
162
|
for (const field of model.fields) {
|
|
137
163
|
if (field.name === 'id')
|
|
138
164
|
continue; // Already added
|
|
165
|
+
validateIdentifier(field.name, `field name in model "${model.name}"`);
|
|
139
166
|
const colType = this.mapFieldType(field.type);
|
|
140
167
|
const notNull = field.required ? ' NOT NULL' : '';
|
|
141
168
|
const defaultVal = field.default !== undefined ? ` DEFAULT ${JSON.stringify(field.default)}` : '';
|
|
@@ -148,14 +175,13 @@ export class SQLiteAdapter {
|
|
|
148
175
|
for (const index of model.indexes) {
|
|
149
176
|
const indexName = `idx_${model.name}_${index.fields.join('_')}`;
|
|
150
177
|
const unique = index.unique ? 'UNIQUE ' : '';
|
|
178
|
+
for (const f of index.fields)
|
|
179
|
+
validateIdentifier(f, `index field name in model "${model.name}"`);
|
|
151
180
|
const cols = index.fields.map((f) => `"${f}"`).join(', ');
|
|
152
181
|
this.db.exec(`CREATE ${unique}INDEX IF NOT EXISTS "${indexName}" ON "${model.name}" (${cols})`);
|
|
153
182
|
}
|
|
154
183
|
}
|
|
155
184
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Map Loom field types to SQLite types
|
|
158
|
-
*/
|
|
159
185
|
mapFieldType(type) {
|
|
160
186
|
switch (type) {
|
|
161
187
|
case 'string':
|
|
@@ -173,9 +199,6 @@ export class SQLiteAdapter {
|
|
|
173
199
|
return 'TEXT';
|
|
174
200
|
}
|
|
175
201
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Serialize a value for SQLite storage
|
|
178
|
-
*/
|
|
179
202
|
serializeValue(value, model, field) {
|
|
180
203
|
if (value === null || value === undefined)
|
|
181
204
|
return null;
|
|
@@ -188,9 +211,6 @@ export class SQLiteAdapter {
|
|
|
188
211
|
return value ? 1 : 0;
|
|
189
212
|
return value;
|
|
190
213
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Deserialize a row from SQLite storage
|
|
193
|
-
*/
|
|
194
214
|
deserializeRow(row, model) {
|
|
195
215
|
const schema = this.schemas.get(model);
|
|
196
216
|
if (!schema)
|
|
@@ -216,10 +236,5 @@ export class SQLiteAdapter {
|
|
|
216
236
|
}
|
|
217
237
|
return result;
|
|
218
238
|
}
|
|
219
|
-
generateId() {
|
|
220
|
-
const timestamp = Date.now().toString(36);
|
|
221
|
-
const random = Math.random().toString(36).substring(2, 10);
|
|
222
|
-
return `rec_${timestamp}_${random}`;
|
|
223
|
-
}
|
|
224
239
|
}
|
|
225
240
|
//# sourceMappingURL=adapter-sqlite.js.map
|