@promakeai/orm 1.0.6 → 1.3.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @promakeai/orm
2
2
 
3
- Core ORM package with schema DSL, query builder, and multi-language support. Platform-agnostic - works in both browser and Node.js.
3
+ Core ORM package with query builder, schema validation, and multi-language support. Platform-agnostic - works in both browser and Node.js.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,144 +8,78 @@ Core ORM package with schema DSL, query builder, and multi-language support. Pla
8
8
  npm install @promakeai/orm
9
9
  ```
10
10
 
11
- ## Schema Definition
12
-
13
- ```typescript
14
- import { defineSchema, f } from '@promakeai/orm';
15
-
16
- const schema = defineSchema({
17
- name: 'myapp',
18
- languages: ['en', 'tr', 'de'],
19
- tables: {
20
- users: {
21
- id: f.id(),
22
- email: f.string().required().unique().lowercase(),
23
- name: f.string().required().trim(),
24
- age: f.int().min(0).max(150),
25
- role: f.string().enum(['user', 'admin']).default('user'),
26
- bio: f.text().translatable(),
27
- createdAt: f.timestamp(),
28
- metadata: f.json(),
29
- },
30
- products: {
31
- id: f.id(),
32
- sku: f.string().required().unique(),
33
- price: f.decimal().required().min(0),
34
- stock: f.int().default(0),
35
- name: f.string().translatable().required(),
36
- description: f.text().translatable(),
37
- categoryId: f.int().ref('categories'),
38
- tagIds: f.json().ref('tags'), // Array reference
39
- },
40
- categories: {
41
- id: f.id(),
42
- slug: f.string().required().unique(),
43
- name: f.string().translatable().required(),
44
- parentId: f.int().ref({
45
- table: 'categories',
46
- onDelete: 'SET_NULL',
47
- }),
48
- },
49
- },
50
- });
51
- ```
52
-
53
- ## JSON Schema (AI-Friendly)
54
-
55
- The JSON schema format is the runtime-friendly version and supports native
56
- arrays and typed objects:
57
-
58
- ```json
59
- {
60
- "name": "myapp",
61
- "languages": ["en", "tr"],
62
- "defaultLanguage": "en",
63
- "tables": {
64
- "products": {
65
- "id": { "type": "id" },
66
- "tags": { "type": ["string"] },
67
- "metadata": { "type": { "color": "string", "weight": "number" } },
68
- "variants": { "type": [{ "sku": "string", "price": "number" }] }
69
- }
70
- }
71
- }
72
- ```
73
-
74
- ## Field Types
75
-
76
- | Type | SQL | Description |
77
- |------|-----|-------------|
78
- | `f.id()` | INTEGER PRIMARY KEY AUTOINCREMENT | Auto-increment primary key |
79
- | `f.string()` | VARCHAR | Short text |
80
- | `f.text()` | TEXT | Long text |
81
- | `f.int()` | INTEGER | Integer |
82
- | `f.decimal()` | REAL/DECIMAL | Decimal number |
83
- | `f.bool()` | INTEGER (0/1) | Boolean |
84
- | `f.timestamp()` | TEXT (ISO) | ISO datetime string |
85
- | `f.json()` | TEXT | JSON serialized data |
86
-
87
- JSON schema type syntax supports `string[]`, `number[]`, `boolean[]`, `object`,
88
- and `object[]` which are stored as JSON in SQL (TEXT) and typed in TS.
89
-
90
- ## Field Modifiers
91
-
92
- ### Constraints
93
-
94
- ```typescript
95
- f.string()
96
- .required() // NOT NULL
97
- .nullable() // Allow NULL (default)
98
- .unique() // UNIQUE constraint
99
- .primary() // PRIMARY KEY
100
- .default('value') // DEFAULT value
101
- ```
102
-
103
- ### String Transforms
104
-
105
- ```typescript
106
- f.string()
107
- .trim() // Remove whitespace
108
- .lowercase() // Convert to lowercase
109
- .uppercase() // Convert to uppercase
110
- ```
111
-
112
- ### Validation
113
-
114
- ```typescript
115
- f.string()
116
- .minLength(1) // Minimum length
117
- .maxLength(255) // Maximum length
118
- .enum(['a', 'b', 'c']) // Allowed values
119
- .match(/^[a-z]+$/) // RegExp pattern
120
-
121
- f.int()
122
- .min(0) // Minimum value
123
- .max(100) // Maximum value
124
- ```
125
-
126
- ### References
127
-
128
- ```typescript
129
- // Simple reference
130
- f.int().ref('users')
131
-
132
- // With options
133
- f.int().ref({
134
- table: 'users',
135
- field: 'id', // Default: 'id'
136
- onDelete: 'CASCADE', // CASCADE | SET_NULL | RESTRICT | NO_ACTION
137
- onUpdate: 'CASCADE',
138
- })
139
-
140
- // Array reference (JSON field with refs)
141
- f.json().ref('tags')
11
+ ## JSON Schema (AI-Friendly)
12
+
13
+ The JSON schema format is the runtime-friendly version and supports native
14
+ arrays, typed objects, and role-based `$permissions`:
15
+
16
+ ```json
17
+ {
18
+ "name": "myapp",
19
+ "languages": ["en", "tr"],
20
+ "defaultLanguage": "en",
21
+ "tables": {
22
+ "products": {
23
+ "$permissions": {
24
+ "anon": ["read"],
25
+ "user": ["read"],
26
+ "admin": ["read", "create", "update", "delete"]
27
+ },
28
+ "id": { "type": "id" },
29
+ "tags": { "type": ["string"] },
30
+ "metadata": { "type": { "color": "string", "weight": "number" } },
31
+ "variants": { "type": [{ "sku": "string", "price": "number" }] }
32
+ }
33
+ }
34
+ }
142
35
  ```
143
36
 
144
- ### Multi-Language
145
-
146
- ```typescript
147
- f.string().translatable() // Stored in {table}_translations
148
- f.text().translatable()
37
+ ## Field Types
38
+
39
+ | Type | SQL | Description |
40
+ |------|-----|-------------|
41
+ | `"id"` | INTEGER PRIMARY KEY AUTOINCREMENT | Auto-increment primary key |
42
+ | `"string"` | VARCHAR | Short text |
43
+ | `"text"` | TEXT | Long text |
44
+ | `"int"` | INTEGER | Integer |
45
+ | `"decimal"` | REAL/DECIMAL | Decimal number |
46
+ | `"bool"` | INTEGER (0/1) | Boolean |
47
+ | `"timestamp"` | TEXT (ISO) | ISO datetime string |
48
+ | `"json"` | TEXT | JSON serialized data |
49
+
50
+ JSON schema type syntax supports `["string"]`, `["number"]`, `["boolean"]`, `{ "key": "string" }`,
51
+ and `[{ "key": "string" }]` which are stored as JSON in SQL (TEXT) and typed in TS.
52
+
53
+ ## Field Modifiers (JSON)
54
+
55
+ ```json
56
+ {
57
+ "email": {
58
+ "type": "string",
59
+ "required": true,
60
+ "unique": true,
61
+ "lowercase": true,
62
+ "trim": true,
63
+ "minLength": 1,
64
+ "maxLength": 255,
65
+ "enum": ["a", "b", "c"],
66
+ "match": "^[a-z]+$",
67
+ "default": "value"
68
+ },
69
+ "age": {
70
+ "type": "int",
71
+ "min": 0,
72
+ "max": 150
73
+ },
74
+ "categoryId": {
75
+ "type": "int",
76
+ "ref": "categories"
77
+ },
78
+ "name": {
79
+ "type": "string",
80
+ "translatable": true
81
+ }
82
+ }
149
83
  ```
150
84
 
151
85
  ## Query Builder (MongoDB-style)
@@ -172,14 +106,14 @@ buildWhereClause({ id: { $nin: [1, 2, 3] } }); // NOT IN (?, ?, ?)
172
106
  buildWhereClause({ name: { $like: '%john%' } }); // LIKE ?
173
107
  buildWhereClause({ name: { $notLike: '%test%' } }); // NOT LIKE ?
174
108
 
175
- // Range and null
176
- buildWhereClause({ price: { $between: [10, 100] } }); // BETWEEN ? AND ?
177
- buildWhereClause({ deletedAt: { $isNull: true } }); // IS NULL
178
- buildWhereClause({ email: { $isNull: false } }); // IS NOT NULL
179
-
180
- // JSON array contains
181
- buildWhereClause({ tags: { $contains: "sale" } }); // json_each(...) = "sale"
182
- buildWhereClause({ tags: { $containsAny: ["sale", "new"] } }); // json_each(...) IN (...)
109
+ // Range and null
110
+ buildWhereClause({ price: { $between: [10, 100] } }); // BETWEEN ? AND ?
111
+ buildWhereClause({ deletedAt: { $isNull: true } }); // IS NULL
112
+ buildWhereClause({ email: { $isNull: false } }); // IS NOT NULL
113
+
114
+ // JSON array contains
115
+ buildWhereClause({ tags: { $contains: "sale" } }); // json_each(...) = "sale"
116
+ buildWhereClause({ tags: { $containsAny: ["sale", "new"] } }); // json_each(...) IN (...)
183
117
 
184
118
  // Logical operators
185
119
  buildWhereClause({
@@ -225,23 +159,19 @@ buildWhereClause({
225
159
 
226
160
  ### Schema Configuration
227
161
 
228
- ```typescript
229
- const schema = defineSchema({
230
- languages: ['en', 'tr', 'de'], // Supported languages
231
- // OR
232
- languages: {
233
- default: 'en',
234
- supported: ['en', 'tr', 'de'],
235
- },
236
- tables: {
237
- products: {
238
- id: f.id(),
239
- price: f.decimal(),
240
- name: f.string().translatable(), // In translation table
241
- description: f.text().translatable(), // In translation table
242
- },
243
- },
244
- });
162
+ ```json
163
+ {
164
+ "languages": ["en", "tr", "de"],
165
+ "defaultLanguage": "en",
166
+ "tables": {
167
+ "products": {
168
+ "id": { "type": "id" },
169
+ "price": { "type": "decimal" },
170
+ "name": { "type": "string", "translatable": true },
171
+ "description": { "type": "text", "translatable": true }
172
+ }
173
+ }
174
+ }
245
175
  ```
246
176
 
247
177
  ### Generated Tables
@@ -290,12 +220,12 @@ const { sql, params } = buildTranslationQuery('products', schema, {
290
220
  // LIMIT 10
291
221
  ```
292
222
 
293
- ## Populate Resolver
294
-
295
- Batch-fetches references to prevent N+1 queries:
296
-
297
- Populate options accept a space-separated string, string array, or nested object
298
- for deeper population.
223
+ ## Populate Resolver
224
+
225
+ Batch-fetches references to prevent N+1 queries:
226
+
227
+ Populate options accept a space-separated string, string array, or nested object
228
+ for deeper population.
299
229
 
300
230
  ```typescript
301
231
  import { resolvePopulate } from '@promakeai/orm';
@@ -335,6 +265,56 @@ const postsWithTags = await resolvePopulate(
335
265
  );
336
266
  ```
337
267
 
268
+ ## Permissions (`$permissions`)
269
+
270
+ Role-based access control defined as table metadata in the JSON schema:
271
+
272
+ ```json
273
+ "$permissions": {
274
+ "anon": ["read"],
275
+ "user": ["read", "create"],
276
+ "admin": ["read", "create", "update", "delete"]
277
+ }
278
+ ```
279
+
280
+ - **Roles:** `anon`, `user`, `admin`
281
+ - **Actions:** `create`, `read`, `update`, `delete`
282
+ - Not a database column — stored as metadata on `TableDefinition.permissions`
283
+
284
+ ```typescript
285
+ import { getTablePermissions } from '@promakeai/orm';
286
+ import type { PermissionRole, PermissionAction, TablePermissions } from '@promakeai/orm';
287
+
288
+ const permissions = getTablePermissions(schema.tables.products);
289
+ // { anon: ['read'], user: ['read'], admin: ['read', 'create', 'update', 'delete'] }
290
+ ```
291
+
292
+ ## REST Adapter
293
+
294
+ REST API adapter with Bearer auth support. Works in both Node.js and browser.
295
+
296
+ ```typescript
297
+ import { RestAdapter } from '@promakeai/orm';
298
+
299
+ // Static token (server-side / CLI)
300
+ const adapter = new RestAdapter({
301
+ baseUrl: 'https://api.example.com',
302
+ token: 'my-api-key',
303
+ schema,
304
+ defaultLang: 'en',
305
+ });
306
+
307
+ // Dynamic token callback (browser — called on every request)
308
+ const adapter = new RestAdapter({
309
+ baseUrl: 'https://api.example.com',
310
+ getToken: () => localStorage.getItem('token'),
311
+ schema,
312
+ defaultLang: 'en',
313
+ });
314
+ ```
315
+
316
+ `getToken` takes priority over static `token` when both are provided.
317
+
338
318
  ## Schema Helpers
339
319
 
340
320
  ```typescript
@@ -347,6 +327,7 @@ import {
347
327
  isRequiredField,
348
328
  getMainTableFields,
349
329
  getTranslationTableFields,
330
+ getTablePermissions,
350
331
  toTranslationTableName,
351
332
  toTranslationFKName,
352
333
  singularize,
@@ -396,23 +377,6 @@ if (isValidSchema(schema)) {
396
377
  }
397
378
  ```
398
379
 
399
- ## Schema Merging
400
-
401
- ```typescript
402
- import { mergeSchemas } from '@promakeai/orm';
403
-
404
- const schema1 = defineSchema({
405
- tables: { users: { ... } },
406
- });
407
-
408
- const schema2 = defineSchema({
409
- tables: { products: { ... } },
410
- });
411
-
412
- const merged = mergeSchemas([schema1, schema2]);
413
- // { tables: { users: {...}, products: {...} } }
414
- ```
415
-
416
380
  ## IDataAdapter Interface
417
381
 
418
382
  All adapters must implement this interface:
@@ -477,7 +441,13 @@ import type {
477
441
  PopulateOption,
478
442
  PaginatedResult,
479
443
  IDataAdapter,
444
+ PermissionRole,
445
+ PermissionAction,
446
+ TablePermissions,
480
447
  } from '@promakeai/orm';
448
+
449
+ import { RestAdapter } from '@promakeai/orm';
450
+ import type { RestAdapterConfig } from '@promakeai/orm';
481
451
  ```
482
452
 
483
453
  ## Related Packages
@@ -0,0 +1,94 @@
1
+ /**
2
+ * REST API Adapter
3
+ *
4
+ * Communicates with a backend REST API implementing the /database endpoints.
5
+ * Works in both Node.js and browser environments (uses fetch).
6
+ */
7
+ import type { IDataAdapter } from "./IDataAdapter";
8
+ import type { QueryOptions, PaginatedResult, SchemaDefinition } from "../types";
9
+ type FetchFn = (input: string, init?: any) => Promise<any>;
10
+ export interface RestAdapterConfig {
11
+ /** Base URL for API (e.g., "https://backend.promake.ai/api") */
12
+ baseUrl: string;
13
+ /** Database endpoint prefix (default: "/database") */
14
+ databasePrefix?: string;
15
+ /** Static authorization token for Bearer auth */
16
+ token?: string;
17
+ /** Dynamic token getter - called on every request. Takes priority over static token. */
18
+ getToken?: () => string | null;
19
+ /** Custom headers to include in all requests */
20
+ headers?: Record<string, string>;
21
+ /** Schema definition for translation support */
22
+ schema?: SchemaDefinition;
23
+ /** Default language for translations */
24
+ defaultLang?: string;
25
+ /** Custom fetch function (for testing/SSR) */
26
+ fetch?: FetchFn;
27
+ /** Request timeout in ms (default: 30000) */
28
+ timeout?: number;
29
+ }
30
+ export declare class RestAdapter implements IDataAdapter {
31
+ private baseUrl;
32
+ private databasePrefix;
33
+ private token?;
34
+ private getTokenFn?;
35
+ private customHeaders;
36
+ private fetchFn;
37
+ private timeout;
38
+ schema?: SchemaDefinition;
39
+ defaultLang?: string;
40
+ constructor(config: RestAdapterConfig);
41
+ private buildUrl;
42
+ private getHeaders;
43
+ private request;
44
+ private buildQueryParams;
45
+ private urlWithParams;
46
+ setSchema(schema: SchemaDefinition): void;
47
+ connect(): Promise<void>;
48
+ private mapFieldType;
49
+ close(): void;
50
+ list<T = unknown>(table: string, options?: QueryOptions): Promise<T[]>;
51
+ get<T = unknown>(table: string, id: string | number, options?: QueryOptions): Promise<T | null>;
52
+ findOne<T = unknown>(table: string, options?: QueryOptions): Promise<T | null>;
53
+ count(table: string, options?: QueryOptions): Promise<number>;
54
+ paginate<T = unknown>(table: string, page: number, limit: number, options?: QueryOptions): Promise<PaginatedResult<T>>;
55
+ create<T = unknown>(table: string, data: Record<string, unknown>): Promise<T>;
56
+ update<T = unknown>(table: string, id: string | number, data: Record<string, unknown>, options?: {
57
+ translations?: Record<string, Record<string, unknown>>;
58
+ }): Promise<T>;
59
+ delete(table: string, id: string | number): Promise<boolean>;
60
+ createMany<T = unknown>(table: string, records: Record<string, unknown>[], options?: {
61
+ ignore?: boolean;
62
+ noAtomic?: boolean;
63
+ }): Promise<{
64
+ created: number;
65
+ ids: (number | bigint)[];
66
+ }>;
67
+ updateMany(table: string, updates: {
68
+ id: number | string;
69
+ data: Record<string, unknown>;
70
+ }[], options?: {
71
+ noAtomic?: boolean;
72
+ }): Promise<{
73
+ updated: number;
74
+ }>;
75
+ deleteMany(table: string, ids: (number | string)[], options?: {
76
+ noAtomic?: boolean;
77
+ }): Promise<{
78
+ deleted: number;
79
+ }>;
80
+ createWithTranslations<T = unknown>(table: string, data: Record<string, unknown>, translations?: Record<string, Record<string, unknown>>): Promise<T>;
81
+ upsertTranslation(table: string, id: string | number, lang: string, data: Record<string, unknown>): Promise<void>;
82
+ getTranslations<T = unknown>(table: string, id: string | number): Promise<T[]>;
83
+ raw<T = unknown>(query: string, params?: unknown[]): Promise<T[]>;
84
+ execute(query: string, params?: unknown[]): Promise<{
85
+ changes: number;
86
+ lastInsertRowid: number | bigint;
87
+ }>;
88
+ beginTransaction(): Promise<void>;
89
+ commit(): Promise<void>;
90
+ rollback(): Promise<void>;
91
+ getTables(): Promise<string[]>;
92
+ getTableSchema(table: string): Promise<unknown[]>;
93
+ }
94
+ export {};
package/dist/index.d.ts CHANGED
@@ -5,21 +5,21 @@
5
5
  *
6
6
  * @example
7
7
  * ```typescript
8
- * import { ORM, defineSchema, f } from '@promakeai/orm';
8
+ * import { ORM, parseJSONSchema } from '@promakeai/orm';
9
9
  *
10
- * // Define schema
11
- * const schema = defineSchema({
10
+ * // Parse JSON schema
11
+ * const schema = parseJSONSchema({
12
12
  * languages: ['en', 'tr'],
13
13
  * tables: {
14
14
  * products: {
15
- * id: f.id(),
16
- * name: f.string().translatable().required(),
17
- * price: f.decimal().required(),
18
- * categoryId: f.int().ref('categories'),
15
+ * id: { type: 'id' },
16
+ * name: { type: 'string', translatable: true, required: true },
17
+ * price: { type: 'decimal', required: true },
18
+ * categoryId: { type: 'int', ref: 'categories' },
19
19
  * },
20
20
  * categories: {
21
- * id: f.id(),
22
- * name: f.string().translatable().required(),
21
+ * id: { type: 'id' },
22
+ * name: { type: 'string', translatable: true, required: true },
23
23
  * },
24
24
  * },
25
25
  * });
@@ -34,12 +34,11 @@
34
34
  */
35
35
  export { ORM } from "./ORM";
36
36
  export type { IDataAdapter } from "./adapters/IDataAdapter";
37
- export { defineSchema, f, mergeSchemas, createSchemaUnsafe, } from "./schema";
38
- export type { FieldBuilder } from "./schema";
37
+ export { RestAdapter, type RestAdapterConfig } from "./adapters/RestAdapter";
39
38
  export { validateSchema, assertValidSchema, isValidSchema, validateTable, ValidationErrorCode, } from "./schema";
40
39
  export type { ValidationError } from "./schema";
41
40
  export { singularize, pluralize, toPascalCase, toCamelCase, toSnakeCase, toInterfaceName, toDbInterfaceName, toPascalCasePlural, toTranslationTableName, toTranslationFKName, } from "./schema";
42
- export { getTranslatableFields, getNonTranslatableFields, getInsertableFields, hasTranslatableFields, getPrimaryKeyField, getReferenceFields, getMainTableFields, getTranslationTableFields, isRequiredField, getRequiredFields, getRefTarget, getRefTargetFull, } from "./schema";
41
+ export { getTranslatableFields, getNonTranslatableFields, getInsertableFields, hasTranslatableFields, getPrimaryKeyField, getReferenceFields, getMainTableFields, getTranslationTableFields, isRequiredField, getRequiredFields, getRefTarget, getRefTargetFull, getTablePermissions, } from "./schema";
43
42
  export { buildWhereClause } from "./utils/whereBuilder";
44
43
  export type { WhereResult } from "./utils/whereBuilder";
45
44
  export { buildTranslationQuery, buildTranslationQueryById, buildTranslationInsert, buildTranslationUpsert, extractTranslatableData, } from "./utils/translationQuery";
@@ -50,4 +49,4 @@ export { parseJSONSchema, parseJSONSchemaWithWarnings, } from "./utils/jsonConve
50
49
  export type { ParseJSONSchemaResult } from "./utils/jsonConverter";
51
50
  export { isJsonType } from "./types";
52
51
  export { deserializeRow, serializeRow } from "./utils/deserializer";
53
- export type { FieldType, FieldDefinition, FieldReference, FieldBuilderLike, TableDefinition, LanguageConfig, SchemaDefinition, SchemaInput, JSONFieldType, JSONFieldDefinition, JSONTableDefinition, JSONSchemaDefinition, WhereCondition, OrderByOption, PopulateOption, PopulateNested, QueryOptions, PaginatedResult, ORMConfig, } from "./types";
52
+ export type { FieldType, FieldDefinition, FieldReference, TableDefinition, LanguageConfig, SchemaDefinition, PermissionRole, PermissionAction, TablePermissions, JSONFieldType, JSONFieldDefinition, JSONTableDefinition, JSONSchemaDefinition, WhereCondition, OrderByOption, PopulateOption, PopulateNested, QueryOptions, PaginatedResult, ORMConfig, } from "./types";