@truto/ginger 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +597 -0
- package/dist/adapters/bun-sqlite.d.ts +37 -0
- package/dist/adapters/bun-sqlite.d.ts.map +1 -0
- package/dist/adapters/bun-sqlite.js +136 -0
- package/dist/adapters/bun-sqlite.js.map +1 -0
- package/dist/adapters/durable-object.d.ts +40 -0
- package/dist/adapters/durable-object.d.ts.map +1 -0
- package/dist/adapters/durable-object.js +142 -0
- package/dist/adapters/durable-object.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/crypto.d.ts +40 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +148 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +90 -0
- package/dist/errors.js.map +1 -0
- package/dist/example.d.ts +119 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +297 -0
- package/dist/example.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination.d.ts +31 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +173 -0
- package/dist/pagination.js.map +1 -0
- package/dist/service.d.ts +81 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +615 -0
- package/dist/service.js.map +1 -0
- package/dist/sql-builder.d.ts +48 -0
- package/dist/sql-builder.d.ts.map +1 -0
- package/dist/sql-builder.js +230 -0
- package/dist/sql-builder.js.map +1 -0
- package/dist/types.d.ts +266 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +94 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base service error class
|
|
3
|
+
*/
|
|
4
|
+
export class ServiceError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
statusCode;
|
|
7
|
+
details;
|
|
8
|
+
constructor(message, code, statusCode = 500, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = this.constructor.name;
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.details = details;
|
|
14
|
+
// Restore prototype chain for instanceof checks
|
|
15
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resource not found error
|
|
20
|
+
*/
|
|
21
|
+
export class NotFoundError extends ServiceError {
|
|
22
|
+
constructor(resource, id, details) {
|
|
23
|
+
super(`${resource} with id "${id}" not found`, 'NOT_FOUND', 404, details);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validation error for schema validation failures
|
|
28
|
+
*/
|
|
29
|
+
export class ValidationError extends ServiceError {
|
|
30
|
+
constructor(message, details) {
|
|
31
|
+
super(message, 'VALIDATION_ERROR', 400, details);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Authorization error for access control failures
|
|
36
|
+
*/
|
|
37
|
+
export class AuthError extends ServiceError {
|
|
38
|
+
constructor(message = 'Access denied', details) {
|
|
39
|
+
super(message, 'AUTH_ERROR', 403, details);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Database operation error
|
|
44
|
+
*/
|
|
45
|
+
export class DatabaseError extends ServiceError {
|
|
46
|
+
constructor(message, details) {
|
|
47
|
+
super(message, 'DATABASE_ERROR', 500, details);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Encryption/decryption error
|
|
52
|
+
*/
|
|
53
|
+
export class EncryptionError extends ServiceError {
|
|
54
|
+
constructor(message, details) {
|
|
55
|
+
super(message, 'ENCRYPTION_ERROR', 500, details);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Hook execution error
|
|
60
|
+
*/
|
|
61
|
+
export class HookError extends ServiceError {
|
|
62
|
+
constructor(hookPhase, methodName, originalError, details) {
|
|
63
|
+
super(`Hook error in ${hookPhase} phase of ${methodName}: ${originalError.message}`, 'HOOK_ERROR', 500, details ? { originalError, ...details } : { originalError });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Pagination cursor error
|
|
68
|
+
*/
|
|
69
|
+
export class CursorError extends ServiceError {
|
|
70
|
+
constructor(message, details) {
|
|
71
|
+
super(message, 'CURSOR_ERROR', 400, details);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* SQL builder error
|
|
76
|
+
*/
|
|
77
|
+
export class SqlBuilderError extends ServiceError {
|
|
78
|
+
constructor(message, details) {
|
|
79
|
+
super(message, 'SQL_BUILDER_ERROR', 500, details);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Dependency injection error
|
|
84
|
+
*/
|
|
85
|
+
export class DependencyError extends ServiceError {
|
|
86
|
+
constructor(message, details) {
|
|
87
|
+
super(message, 'DEPENDENCY_ERROR', 500, details);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrB,IAAI,CAAQ;IACZ,UAAU,CAAQ;IAClB,OAAO,CAAU;IAEjC,YACE,OAAe,EACf,IAAY,EACZ,aAAqB,GAAG,EACxB,OAAiB;QAEjB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAA;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QAEtB,gDAAgD;QAChD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACnD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,YAAY;IAC7C,YAAY,QAAgB,EAAE,EAAmB,EAAE,OAAiB;QAClE,KAAK,CAAC,GAAG,QAAQ,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAC3E,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAe,EAAE,OAAiB;QAC5C,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAClD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,YAAY;IACzC,YAAY,UAAkB,eAAe,EAAE,OAAiB;QAC9D,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAC5C,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,YAAY;IAC7C,YAAY,OAAe,EAAE,OAAiB;QAC5C,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAChD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAe,EAAE,OAAiB;QAC5C,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAClD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,YAAY;IACzC,YACE,SAAiB,EACjB,UAAkB,EAClB,aAAoB,EACpB,OAAiB;QAEjB,KAAK,CACH,iBAAiB,SAAS,aAAa,UAAU,KAAK,aAAa,CAAC,OAAO,EAAE,EAC7E,YAAY,EACZ,GAAG,EACH,OAAO,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAC5D,CAAA;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,WAAY,SAAQ,YAAY;IAC3C,YAAY,OAAe,EAAE,OAAiB;QAC5C,KAAK,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAe,EAAE,OAAiB;QAC5C,KAAK,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IACnD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAe,EAAE,OAAiB;QAC5C,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAClD,CAAC;CACF"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complete example of using the Ginger library with:
|
|
3
|
+
* - Secret apiKey field
|
|
4
|
+
* - Join to teams table
|
|
5
|
+
* - Custom method withMembership
|
|
6
|
+
* - Hook that enforces tenant filtering via auth.user
|
|
7
|
+
*/
|
|
8
|
+
import { z } from './index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Factory function to create users service with tenant hooks and encryption keys
|
|
11
|
+
*/
|
|
12
|
+
export declare function createUsersService(db: any, encryptionKeys: Record<string, string>): import("./service.js").Service<z.ZodObject<{
|
|
13
|
+
id: z.ZodNumber;
|
|
14
|
+
name: z.ZodString;
|
|
15
|
+
email: z.ZodString;
|
|
16
|
+
apiKey: z.ZodString;
|
|
17
|
+
tenantId: z.ZodString;
|
|
18
|
+
createdAt: z.ZodString;
|
|
19
|
+
updatedAt: z.ZodString;
|
|
20
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
21
|
+
name: z.ZodString;
|
|
22
|
+
email: z.ZodString;
|
|
23
|
+
apiKey: z.ZodString;
|
|
24
|
+
tenantId: z.ZodString;
|
|
25
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
26
|
+
name: z.ZodOptional<z.ZodString>;
|
|
27
|
+
email: z.ZodOptional<z.ZodString>;
|
|
28
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>, {
|
|
30
|
+
teams: {
|
|
31
|
+
kind: "many";
|
|
32
|
+
localPk: string;
|
|
33
|
+
through: {
|
|
34
|
+
table: string;
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
};
|
|
38
|
+
remote: {
|
|
39
|
+
table: string;
|
|
40
|
+
pk: string;
|
|
41
|
+
select: string[];
|
|
42
|
+
alias: string;
|
|
43
|
+
};
|
|
44
|
+
where: string;
|
|
45
|
+
schema: z.ZodObject<{
|
|
46
|
+
id: z.ZodNumber;
|
|
47
|
+
name: z.ZodString;
|
|
48
|
+
description: z.ZodString;
|
|
49
|
+
}, z.core.$strip>;
|
|
50
|
+
};
|
|
51
|
+
}, readonly [{
|
|
52
|
+
readonly logicalName: "apiKey";
|
|
53
|
+
readonly columnName: "api_key_encrypted";
|
|
54
|
+
readonly keyId: "user-secrets";
|
|
55
|
+
}]>;
|
|
56
|
+
/**
|
|
57
|
+
* Example usage in a Cloudflare Worker
|
|
58
|
+
*/
|
|
59
|
+
declare const _default: {
|
|
60
|
+
fetch(_request: Request, env: any, _ctx: any): Promise<Response>;
|
|
61
|
+
};
|
|
62
|
+
export default _default;
|
|
63
|
+
/**
|
|
64
|
+
* Cloudflare Worker Environment Setup:
|
|
65
|
+
*
|
|
66
|
+
* 1. Add these environment variables to your Cloudflare Worker:
|
|
67
|
+
* - ENCRYPTION_KEY_DEFAULT: A base64-encoded 256-bit encryption key
|
|
68
|
+
* - ENCRYPTION_KEY_USER_SECRETS: (Optional) A separate key for user secrets
|
|
69
|
+
*
|
|
70
|
+
* 2. Generate encryption keys using the library:
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { generateSecretKey } from './crypto.js'
|
|
73
|
+
* const key = await generateSecretKey()
|
|
74
|
+
* console.log('ENCRYPTION_KEY_DEFAULT=' + key)
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* 3. Bind your D1 database as "DB" in wrangler.toml:
|
|
78
|
+
* ```toml
|
|
79
|
+
* [[d1_databases]]
|
|
80
|
+
* binding = "DB"
|
|
81
|
+
* database_name = "your-database"
|
|
82
|
+
* database_id = "your-database-id"
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* SQL schema for this example:
|
|
87
|
+
*
|
|
88
|
+
* ```sql
|
|
89
|
+
* CREATE TABLE users (
|
|
90
|
+
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
91
|
+
* name TEXT NOT NULL,
|
|
92
|
+
* email TEXT NOT NULL UNIQUE,
|
|
93
|
+
* api_key_encrypted TEXT NOT NULL,
|
|
94
|
+
* tenant_id TEXT NOT NULL,
|
|
95
|
+
* created_at TEXT NOT NULL,
|
|
96
|
+
* updated_at TEXT NOT NULL
|
|
97
|
+
* );
|
|
98
|
+
*
|
|
99
|
+
* CREATE TABLE teams (
|
|
100
|
+
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
* name TEXT NOT NULL,
|
|
102
|
+
* description TEXT,
|
|
103
|
+
* tenant_id TEXT NOT NULL,
|
|
104
|
+
* active INTEGER DEFAULT 1
|
|
105
|
+
* );
|
|
106
|
+
*
|
|
107
|
+
* CREATE TABLE user_teams (
|
|
108
|
+
* user_id INTEGER NOT NULL,
|
|
109
|
+
* team_id INTEGER NOT NULL,
|
|
110
|
+
* PRIMARY KEY (user_id, team_id),
|
|
111
|
+
* FOREIGN KEY (user_id) REFERENCES users(id),
|
|
112
|
+
* FOREIGN KEY (team_id) REFERENCES teams(id)
|
|
113
|
+
* );
|
|
114
|
+
*
|
|
115
|
+
* CREATE INDEX idx_users_tenant_id ON users(tenant_id);
|
|
116
|
+
* CREATE INDEX idx_teams_tenant_id ON teams(tenant_id);
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
//# sourceMappingURL=example.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAiB,CAAC,EAAqC,MAAM,YAAY,CAAA;AAoEhF;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,GAAG,EACP,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4DvC;AAED;;GAEG;;oBAEqB,OAAO,OAAO,GAAG,QAAQ,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;;AADxE,wBAyHC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG"}
|
package/dist/example.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complete example of using the Ginger library with:
|
|
3
|
+
* - Secret apiKey field
|
|
4
|
+
* - Join to teams table
|
|
5
|
+
* - Custom method withMembership
|
|
6
|
+
* - Hook that enforces tenant filtering via auth.user
|
|
7
|
+
*/
|
|
8
|
+
import { createService, z } from './index.js';
|
|
9
|
+
// Schema definitions
|
|
10
|
+
const UserRowSchema = z.object({
|
|
11
|
+
id: z.number(),
|
|
12
|
+
name: z.string(),
|
|
13
|
+
email: z.string(),
|
|
14
|
+
apiKey: z.string(), // This will be encrypted
|
|
15
|
+
tenantId: z.string(),
|
|
16
|
+
createdAt: z.string(),
|
|
17
|
+
updatedAt: z.string(),
|
|
18
|
+
});
|
|
19
|
+
const UserCreateSchema = z.object({
|
|
20
|
+
name: z.string().min(1).max(255),
|
|
21
|
+
email: z.string().email().max(255),
|
|
22
|
+
apiKey: z.string().min(32).max(255),
|
|
23
|
+
tenantId: z.string().uuid(),
|
|
24
|
+
});
|
|
25
|
+
const UserUpdateSchema = z.object({
|
|
26
|
+
name: z.string().min(1).max(255).optional(),
|
|
27
|
+
email: z.string().email().max(255).optional(),
|
|
28
|
+
apiKey: z.string().min(32).max(255).optional(),
|
|
29
|
+
});
|
|
30
|
+
const TeamRowSchema = z.object({
|
|
31
|
+
id: z.number(),
|
|
32
|
+
name: z.string(),
|
|
33
|
+
description: z.string(),
|
|
34
|
+
});
|
|
35
|
+
// Join definitions
|
|
36
|
+
// Note: Auth-based filtering (like tenant_id checks) should NOT be embedded in join definitions.
|
|
37
|
+
// Instead, handle them through:
|
|
38
|
+
// 1. Hooks that modify where clauses before queries
|
|
39
|
+
// 2. Proper database design with foreign keys
|
|
40
|
+
// 3. Service-level filtering logic
|
|
41
|
+
// 4. Row-level security in the database
|
|
42
|
+
const userJoins = {
|
|
43
|
+
teams: {
|
|
44
|
+
kind: 'many',
|
|
45
|
+
localPk: 'id',
|
|
46
|
+
through: {
|
|
47
|
+
table: 'user_teams',
|
|
48
|
+
from: 'user_id',
|
|
49
|
+
to: 'team_id',
|
|
50
|
+
},
|
|
51
|
+
remote: {
|
|
52
|
+
table: 'teams',
|
|
53
|
+
pk: 'id',
|
|
54
|
+
select: ['id', 'name', 'description'],
|
|
55
|
+
alias: 'team',
|
|
56
|
+
},
|
|
57
|
+
where: 'teams.active = 1',
|
|
58
|
+
schema: TeamRowSchema,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
// Secret field definitions
|
|
62
|
+
const userSecrets = [
|
|
63
|
+
{
|
|
64
|
+
logicalName: 'apiKey',
|
|
65
|
+
columnName: 'api_key_encrypted',
|
|
66
|
+
keyId: 'user-secrets',
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* Factory function to create users service with tenant hooks and encryption keys
|
|
71
|
+
*/
|
|
72
|
+
export function createUsersService(db, encryptionKeys) {
|
|
73
|
+
return createService({
|
|
74
|
+
table: 'users',
|
|
75
|
+
db,
|
|
76
|
+
rowSchema: UserRowSchema,
|
|
77
|
+
createSchema: UserCreateSchema,
|
|
78
|
+
updateSchema: UserUpdateSchema,
|
|
79
|
+
joins: userJoins,
|
|
80
|
+
secrets: userSecrets,
|
|
81
|
+
primaryKey: 'id',
|
|
82
|
+
defaultOrderBy: { column: 'created_at', direction: 'desc' },
|
|
83
|
+
encryptionKeys, // Pass encryption keys directly
|
|
84
|
+
hooks: {
|
|
85
|
+
// Enforce tenant filtering on all operations
|
|
86
|
+
list: {
|
|
87
|
+
before: async (ctx) => {
|
|
88
|
+
if (!ctx.auth.user?.tenantId) {
|
|
89
|
+
throw new Error('User must have a tenant ID');
|
|
90
|
+
}
|
|
91
|
+
// Add tenant filter to where clause
|
|
92
|
+
if (!ctx.params.where) {
|
|
93
|
+
ctx.params.where = {};
|
|
94
|
+
}
|
|
95
|
+
ctx.params.where.tenantId = ctx.auth.user.tenantId;
|
|
96
|
+
// For joins that need tenant filtering, you can:
|
|
97
|
+
// 1. Ensure proper foreign key relationships in your database schema
|
|
98
|
+
// 2. Use additional where clauses on the main query
|
|
99
|
+
// 3. Implement custom filtering logic in hooks
|
|
100
|
+
// The join definitions remain clean and auth-agnostic
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
get: {
|
|
104
|
+
before: async (ctx) => {
|
|
105
|
+
if (!ctx.auth.user?.tenantId) {
|
|
106
|
+
throw new Error('User must have a tenant ID');
|
|
107
|
+
}
|
|
108
|
+
// This would be handled by ensuring the ID belongs to the tenant
|
|
109
|
+
// In practice, you'd modify the query to include tenant check
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
create: {
|
|
113
|
+
before: async (ctx) => {
|
|
114
|
+
if (!ctx.auth.user?.tenantId) {
|
|
115
|
+
throw new Error('User must have a tenant ID');
|
|
116
|
+
}
|
|
117
|
+
// Ensure created records belong to the user's tenant
|
|
118
|
+
ctx.data.tenantId = ctx.auth.user.tenantId;
|
|
119
|
+
ctx.data.createdAt = new Date().toISOString();
|
|
120
|
+
ctx.data.updatedAt = new Date().toISOString();
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
update: {
|
|
124
|
+
before: async (ctx) => {
|
|
125
|
+
ctx.data.updatedAt = new Date().toISOString();
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Example usage in a Cloudflare Worker
|
|
133
|
+
*/
|
|
134
|
+
export default {
|
|
135
|
+
async fetch(_request, env, _ctx) {
|
|
136
|
+
// Get encryption keys from Cloudflare environment variables/secrets
|
|
137
|
+
const encryptionKeys = {
|
|
138
|
+
default: env.ENCRYPTION_KEY_DEFAULT, // Store as base64 encoded key
|
|
139
|
+
'user-secrets': env.ENCRYPTION_KEY_USER_SECRETS || env.ENCRYPTION_KEY_DEFAULT,
|
|
140
|
+
};
|
|
141
|
+
// Initialize the service with the database binding and encryption keys
|
|
142
|
+
const usersService = createService({
|
|
143
|
+
table: 'users',
|
|
144
|
+
db: env.DB, // Cloudflare D1 binding
|
|
145
|
+
rowSchema: UserRowSchema,
|
|
146
|
+
createSchema: UserCreateSchema,
|
|
147
|
+
updateSchema: UserUpdateSchema,
|
|
148
|
+
joins: userJoins,
|
|
149
|
+
secrets: userSecrets,
|
|
150
|
+
encryptionKeys, // Pass encryption keys directly
|
|
151
|
+
hooks: {
|
|
152
|
+
// Enforce tenant filtering on all operations
|
|
153
|
+
list: {
|
|
154
|
+
before: async (ctx) => {
|
|
155
|
+
if (!ctx.auth.user?.tenantId) {
|
|
156
|
+
throw new Error('User must have a tenant ID');
|
|
157
|
+
}
|
|
158
|
+
// Add tenant filter to where clause
|
|
159
|
+
if (!ctx.params.where) {
|
|
160
|
+
ctx.params.where = {};
|
|
161
|
+
}
|
|
162
|
+
ctx.params.where.tenantId = ctx.auth.user.tenantId;
|
|
163
|
+
// For joins that need tenant filtering, you can:
|
|
164
|
+
// 1. Ensure proper foreign key relationships in your database schema
|
|
165
|
+
// 2. Use additional where clauses on the main query
|
|
166
|
+
// 3. Implement custom filtering logic in hooks
|
|
167
|
+
// The join definitions remain clean and auth-agnostic
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
create: {
|
|
171
|
+
before: async (ctx) => {
|
|
172
|
+
if (!ctx.auth.user?.tenantId) {
|
|
173
|
+
throw new Error('User must have a tenant ID');
|
|
174
|
+
}
|
|
175
|
+
// Ensure created records belong to the user's tenant
|
|
176
|
+
ctx.data.tenantId = ctx.auth.user.tenantId;
|
|
177
|
+
ctx.data.createdAt = new Date().toISOString();
|
|
178
|
+
ctx.data.updatedAt = new Date().toISOString();
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
try {
|
|
184
|
+
// Example: Create a new user
|
|
185
|
+
const newUser = await usersService.create({
|
|
186
|
+
name: 'John Doe',
|
|
187
|
+
email: 'john@example.com',
|
|
188
|
+
apiKey: 'sk_ex_abcdef1234567890abcdef1234567890',
|
|
189
|
+
tenantId: 'tenant-123',
|
|
190
|
+
}, {
|
|
191
|
+
auth: {
|
|
192
|
+
user: {
|
|
193
|
+
id: 'current-user-id',
|
|
194
|
+
tenantId: 'tenant-123',
|
|
195
|
+
roles: ['admin'],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
// Example: List users with pagination
|
|
200
|
+
const users = await usersService.list({
|
|
201
|
+
auth: {
|
|
202
|
+
user: {
|
|
203
|
+
id: 'current-user-id',
|
|
204
|
+
tenantId: 'tenant-123',
|
|
205
|
+
roles: ['admin'],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
limit: 10,
|
|
209
|
+
include: { teams: true },
|
|
210
|
+
orderBy: [{ column: 'created_at', direction: 'desc' }],
|
|
211
|
+
});
|
|
212
|
+
// Example: Get a user with secrets included
|
|
213
|
+
const userWithSecrets = await usersService.get(newUser.id, {
|
|
214
|
+
auth: {
|
|
215
|
+
user: {
|
|
216
|
+
id: 'current-user-id',
|
|
217
|
+
tenantId: 'tenant-123',
|
|
218
|
+
roles: ['admin'],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
includeSecrets: true, // This will decrypt the API key
|
|
222
|
+
});
|
|
223
|
+
return new Response(JSON.stringify({
|
|
224
|
+
newUser,
|
|
225
|
+
users,
|
|
226
|
+
userWithSecrets,
|
|
227
|
+
}), {
|
|
228
|
+
headers: { 'content-type': 'application/json' },
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
return new Response(JSON.stringify({
|
|
233
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
234
|
+
}), {
|
|
235
|
+
status: 500,
|
|
236
|
+
headers: { 'content-type': 'application/json' },
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Cloudflare Worker Environment Setup:
|
|
243
|
+
*
|
|
244
|
+
* 1. Add these environment variables to your Cloudflare Worker:
|
|
245
|
+
* - ENCRYPTION_KEY_DEFAULT: A base64-encoded 256-bit encryption key
|
|
246
|
+
* - ENCRYPTION_KEY_USER_SECRETS: (Optional) A separate key for user secrets
|
|
247
|
+
*
|
|
248
|
+
* 2. Generate encryption keys using the library:
|
|
249
|
+
* ```typescript
|
|
250
|
+
* import { generateSecretKey } from './crypto.js'
|
|
251
|
+
* const key = await generateSecretKey()
|
|
252
|
+
* console.log('ENCRYPTION_KEY_DEFAULT=' + key)
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* 3. Bind your D1 database as "DB" in wrangler.toml:
|
|
256
|
+
* ```toml
|
|
257
|
+
* [[d1_databases]]
|
|
258
|
+
* binding = "DB"
|
|
259
|
+
* database_name = "your-database"
|
|
260
|
+
* database_id = "your-database-id"
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
/**
|
|
264
|
+
* SQL schema for this example:
|
|
265
|
+
*
|
|
266
|
+
* ```sql
|
|
267
|
+
* CREATE TABLE users (
|
|
268
|
+
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
269
|
+
* name TEXT NOT NULL,
|
|
270
|
+
* email TEXT NOT NULL UNIQUE,
|
|
271
|
+
* api_key_encrypted TEXT NOT NULL,
|
|
272
|
+
* tenant_id TEXT NOT NULL,
|
|
273
|
+
* created_at TEXT NOT NULL,
|
|
274
|
+
* updated_at TEXT NOT NULL
|
|
275
|
+
* );
|
|
276
|
+
*
|
|
277
|
+
* CREATE TABLE teams (
|
|
278
|
+
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
279
|
+
* name TEXT NOT NULL,
|
|
280
|
+
* description TEXT,
|
|
281
|
+
* tenant_id TEXT NOT NULL,
|
|
282
|
+
* active INTEGER DEFAULT 1
|
|
283
|
+
* );
|
|
284
|
+
*
|
|
285
|
+
* CREATE TABLE user_teams (
|
|
286
|
+
* user_id INTEGER NOT NULL,
|
|
287
|
+
* team_id INTEGER NOT NULL,
|
|
288
|
+
* PRIMARY KEY (user_id, team_id),
|
|
289
|
+
* FOREIGN KEY (user_id) REFERENCES users(id),
|
|
290
|
+
* FOREIGN KEY (team_id) REFERENCES teams(id)
|
|
291
|
+
* );
|
|
292
|
+
*
|
|
293
|
+
* CREATE INDEX idx_users_tenant_id ON users(tenant_id);
|
|
294
|
+
* CREATE INDEX idx_teams_tenant_id ON teams(tenant_id);
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
//# sourceMappingURL=example.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example.js","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ginger — A type-safe SQLite data access layer
|
|
3
|
+
*
|
|
4
|
+
* Works with Cloudflare D1, Bun SQLite, and Durable Object SqlStorage.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Cursor-based pagination
|
|
8
|
+
* - Type-safe joins with conditional inclusion
|
|
9
|
+
* - AES-256-GCM encryption for secret fields
|
|
10
|
+
* - Comprehensive hook system
|
|
11
|
+
* - Dependency injection
|
|
12
|
+
* - Safe SQL generation
|
|
13
|
+
*/
|
|
14
|
+
export { Service } from './service.js';
|
|
15
|
+
export type { AuthContext, BaseCtx, BaseService, ComputeJoins, CountParams, CreateParams, CursorToken, Database, ExecResult, PreparedStatement, QueryResult, DeleteParams, ErrorCtx, GetParams, Hook, HookMap, HookPhase, JoinDef, JoinKind, KeyProvider, ListParams, ListResult, MethodName, MethodOptions, OrderBy, OrderDirection, QueryParams, SecretFieldDef, ServiceCreate, ServiceDeps, ServiceOptions, ServiceRow, ServiceUpdate, UpdateParams, } from './types.js';
|
|
16
|
+
export { AuthError, CursorError, DatabaseError, DependencyError, EncryptionError, HookError, NotFoundError, ServiceError, SqlBuilderError, ValidationError, } from './errors.js';
|
|
17
|
+
export { decrypt, decryptSecrets, DefaultKeyProvider, encrypt, encryptSecrets, generateSecretKey, getSecretColumns, getSecretLogicalNames, } from './crypto.js';
|
|
18
|
+
export { buildCursorConditions, createCursor, decodeCursor, encodeCursor, getDefaultOrderBy, reverseOrderBy, validateOrderBy, } from './pagination.js';
|
|
19
|
+
export { buildCount, buildDelete, buildInsert, buildSelect, buildSelectById, buildUpdate, escapeColumn, escapeTable, } from './sql-builder.js';
|
|
20
|
+
export { fromBunSqlite, fromDurableObjectStorage } from './adapters/index.js';
|
|
21
|
+
export type { BunSqliteDatabase, BunSqliteStatement, DurableObjectSqlStorage, SqlStorageCursor, } from './adapters/index.js';
|
|
22
|
+
export * as z from 'zod/v4';
|
|
23
|
+
/**
|
|
24
|
+
* Create a new service instance with the provided configuration
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { createService, z } from 'ginger'
|
|
29
|
+
*
|
|
30
|
+
* const userService = createService({
|
|
31
|
+
* table: 'users',
|
|
32
|
+
* db, // Database instance (D1 binding, fromBunSqlite, or fromDurableObjectStorage)
|
|
33
|
+
* rowSchema: z.object({
|
|
34
|
+
* id: z.number(),
|
|
35
|
+
* name: z.string(),
|
|
36
|
+
* email: z.string(),
|
|
37
|
+
* }),
|
|
38
|
+
* createSchema: z.object({
|
|
39
|
+
* name: z.string(),
|
|
40
|
+
* email: z.string(),
|
|
41
|
+
* }),
|
|
42
|
+
* updateSchema: z.object({
|
|
43
|
+
* name: z.string().optional(),
|
|
44
|
+
* email: z.string().optional(),
|
|
45
|
+
* }),
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function createService<TRow extends import('zod/v4').ZodTypeAny, TCreate extends import('zod/v4').ZodTypeAny, TUpdate extends import('zod/v4').ZodTypeAny, TJoins extends Record<string, import('./types.js').JoinDef> = Record<string, never>, TSecrets extends readonly import('./types.js').SecretFieldDef[] | undefined = undefined>(options: import('./types.js').ServiceOptions<TRow, TCreate, TUpdate, TJoins, TSecrets>): import('./service.js').Service<TRow, TCreate, TUpdate, TJoins, TSecrets>;
|
|
50
|
+
declare const _default: {
|
|
51
|
+
createService: typeof createService;
|
|
52
|
+
};
|
|
53
|
+
export default _default;
|
|
54
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAItC,YAAY,EACV,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,WAAW,EAEX,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,OAAO,EACP,SAAS,EAET,OAAO,EACP,QAAQ,EACR,WAAW,EAEX,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,OAAO,EACP,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EAEd,UAAU,EACV,aAAa,EACb,YAAY,GACb,MAAM,YAAY,CAAA;AAGnB,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,eAAe,EACf,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAA;AAGpB,OAAO,EACL,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,OAAO,EACP,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAGpB,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,eAAe,EACf,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAC3B,IAAI,SAAS,OAAO,QAAQ,EAAE,UAAU,EACxC,OAAO,SAAS,OAAO,QAAQ,EAAE,UAAU,EAC3C,OAAO,SAAS,OAAO,QAAQ,EAAE,UAAU,EAC3C,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,YAAY,EAAE,OAAO,CAAC,GAAG,MAAM,CAClE,MAAM,EACN,KAAK,CACN,EACD,QAAQ,SACJ,SAAS,OAAO,YAAY,EAAE,cAAc,EAAE,GAC9C,SAAS,GAAG,SAAS,EAEzB,OAAO,EAAE,OAAO,YAAY,EAAE,cAAc,CAC1C,IAAI,EACJ,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,CACT,GACA,OAAO,cAAc,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAE1E;;;;AAGD,wBAEC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ginger — A type-safe SQLite data access layer
|
|
3
|
+
*
|
|
4
|
+
* Works with Cloudflare D1, Bun SQLite, and Durable Object SqlStorage.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Cursor-based pagination
|
|
8
|
+
* - Type-safe joins with conditional inclusion
|
|
9
|
+
* - AES-256-GCM encryption for secret fields
|
|
10
|
+
* - Comprehensive hook system
|
|
11
|
+
* - Dependency injection
|
|
12
|
+
* - Safe SQL generation
|
|
13
|
+
*/
|
|
14
|
+
// Main Service class
|
|
15
|
+
export { Service } from './service.js';
|
|
16
|
+
import { Service } from './service.js';
|
|
17
|
+
// Error classes
|
|
18
|
+
export { AuthError, CursorError, DatabaseError, DependencyError, EncryptionError, HookError, NotFoundError, ServiceError, SqlBuilderError, ValidationError, } from './errors.js';
|
|
19
|
+
// Encryption utilities
|
|
20
|
+
export { decrypt, decryptSecrets, DefaultKeyProvider, encrypt, encryptSecrets, generateSecretKey, getSecretColumns, getSecretLogicalNames, } from './crypto.js';
|
|
21
|
+
// Pagination utilities
|
|
22
|
+
export { buildCursorConditions, createCursor, decodeCursor, encodeCursor, getDefaultOrderBy, reverseOrderBy, validateOrderBy, } from './pagination.js';
|
|
23
|
+
// SQL builder utilities
|
|
24
|
+
export { buildCount, buildDelete, buildInsert, buildSelect, buildSelectById, buildUpdate, escapeColumn, escapeTable, } from './sql-builder.js';
|
|
25
|
+
// Database adapters
|
|
26
|
+
export { fromBunSqlite, fromDurableObjectStorage } from './adapters/index.js';
|
|
27
|
+
// Re-export zod v4 for convenience
|
|
28
|
+
export * as z from 'zod/v4';
|
|
29
|
+
/**
|
|
30
|
+
* Create a new service instance with the provided configuration
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { createService, z } from 'ginger'
|
|
35
|
+
*
|
|
36
|
+
* const userService = createService({
|
|
37
|
+
* table: 'users',
|
|
38
|
+
* db, // Database instance (D1 binding, fromBunSqlite, or fromDurableObjectStorage)
|
|
39
|
+
* rowSchema: z.object({
|
|
40
|
+
* id: z.number(),
|
|
41
|
+
* name: z.string(),
|
|
42
|
+
* email: z.string(),
|
|
43
|
+
* }),
|
|
44
|
+
* createSchema: z.object({
|
|
45
|
+
* name: z.string(),
|
|
46
|
+
* email: z.string(),
|
|
47
|
+
* }),
|
|
48
|
+
* updateSchema: z.object({
|
|
49
|
+
* name: z.string().optional(),
|
|
50
|
+
* email: z.string().optional(),
|
|
51
|
+
* }),
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createService(options) {
|
|
56
|
+
return new Service(options);
|
|
57
|
+
}
|
|
58
|
+
// Default export for convenience
|
|
59
|
+
export default {
|
|
60
|
+
createService,
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qBAAqB;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AA4CtC,gBAAgB;AAChB,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,eAAe,EACf,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAA;AAEpB,uBAAuB;AACvB,OAAO,EACL,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,OAAO,EACP,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,uBAAuB;AACvB,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,iBAAiB,CAAA;AAExB,wBAAwB;AACxB,OAAO,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,eAAe,EACf,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAEzB,oBAAoB;AACpB,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAQ7E,mCAAmC;AACnC,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,aAAa,CAY3B,OAMC;IAED,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;AAC7B,CAAC;AAED,iCAAiC;AACjC,eAAe;IACb,aAAa;CACd,CAAA"}
|