@elysiumjs/core 0.5.4 → 0.6.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.
@@ -56,6 +56,12 @@ export interface RepositoryInterface<TModel extends ModelClass<TTableName, TColu
56
56
  * @returns All the records in the database.
57
57
  */
58
58
  deleteAll(): Promise<TModel['$inferSelect'][]>;
59
+ /**
60
+ * Checks if a record exists in the database.
61
+ * @param id The ID of the record to check.
62
+ * @returns True if the record exists, false otherwise.
63
+ */
64
+ exists(id: TId): Promise<boolean>;
59
65
  }
60
66
  /**
61
67
  * Type of a repository class.
@@ -89,7 +95,7 @@ export declare const Repository: <TModel extends ModelClass<TTableName, TColumns
89
95
  /**
90
96
  * The database connection used by this repository.
91
97
  */
92
- readonly db: DatabaseConnection;
98
+ get db(): DatabaseConnection;
93
99
  /**
94
100
  * Retrieves all the records in the database.
95
101
  * @returns All the records in the database.
@@ -149,6 +155,12 @@ export declare const Repository: <TModel extends ModelClass<TTableName, TColumns
149
155
  * @returns All the records in the database.
150
156
  */
151
157
  deleteAll(): Promise<TModel["$inferSelect"][]>;
158
+ /**
159
+ * Checks if a record exists in the database.
160
+ * @param id The ID of the record to check.
161
+ * @returns True if the record exists, false otherwise.
162
+ */
163
+ exists(id: TId): Promise<boolean>;
152
164
  };
153
165
  /**
154
166
  * The drizzle's table schema wrapped by this repository.
package/dist/service.d.ts CHANGED
@@ -65,11 +65,19 @@ export declare namespace Service {
65
65
  const inject: (service?: string | ServiceClass) => ParameterDecorator;
66
66
  /**
67
67
  * Retrieves a registered service's instance from the container.
68
+ * Resolves lazy services on first access and caches the result.
68
69
  * @author Axel Nana <axel.nana@workbud.com>
69
70
  * @param service The name of the service, or its class.
70
71
  * @returns An instance of the registered service, or `null` if no service with that name/type was registered.
71
72
  */
72
73
  const get: <T>(service: string | TypedServiceClass<T>) => T | null;
74
+ /**
75
+ * Registers a lazy service that is only instantiated on first access.
76
+ * @author Axel Nana <axel.nana@workbud.com>
77
+ * @param name The name of the service.
78
+ * @param resolver A factory function called on first access.
79
+ */
80
+ const registerLazy: <T>(name: string, resolver: () => T) => void;
73
81
  /**
74
82
  * Instantiates a service with its dependencies.
75
83
  *
@@ -0,0 +1,244 @@
1
+ import type { PgColumnBuilderBase, PgSchema, PgTable, PgTransaction } from 'drizzle-orm/pg-core';
2
+ import type { Context } from './http';
3
+ import { Middleware } from './middleware';
4
+ /**
5
+ * The tenancy mode for the application.
6
+ * - `schema`: Each tenant has their own Postgres schema.
7
+ * - `rls`: Row-Level Security with a shared schema and a tenant column.
8
+ * @author Axel Nana <axel.nana@workbud.com>
9
+ */
10
+ export type TenancyMode = 'schema' | 'rls';
11
+ /**
12
+ * Global tenancy configuration for the application.
13
+ * @author Axel Nana <axel.nana@workbud.com>
14
+ */
15
+ export interface TenancyConfig {
16
+ /**
17
+ * The tenancy mode to use.
18
+ */
19
+ mode: TenancyMode;
20
+ /**
21
+ * Configuration specific to RLS mode.
22
+ */
23
+ rls?: {
24
+ /**
25
+ * The PostgreSQL session variable used to store the current tenant.
26
+ * @default 'app.current_tenant'
27
+ */
28
+ sessionVariable?: string;
29
+ /**
30
+ * The default column name used for tenant isolation in RLS policies.
31
+ * @default 'tenant_id'
32
+ */
33
+ defaultColumn?: string;
34
+ };
35
+ /**
36
+ * Configuration specific to schema-based tenancy.
37
+ */
38
+ schema?: {
39
+ /**
40
+ * Whether to automatically create tenant schemas on first use.
41
+ */
42
+ autoCreate?: boolean;
43
+ };
44
+ }
45
+ /**
46
+ * Per-model tenancy configuration, used to control how a specific model
47
+ * interacts with the tenancy system.
48
+ * @author Axel Nana <axel.nana@workbud.com>
49
+ */
50
+ export interface ModelTenancyConfig {
51
+ /**
52
+ * Whether tenancy is enabled for this model.
53
+ */
54
+ enabled: boolean;
55
+ /**
56
+ * Override the global tenancy mode for this model.
57
+ */
58
+ mode?: TenancyMode;
59
+ /**
60
+ * The column name used for RLS tenant isolation in this model.
61
+ */
62
+ column?: string;
63
+ /**
64
+ * Custom RLS policy name for this model.
65
+ */
66
+ policyName?: string;
67
+ }
68
+ /**
69
+ * Sets the global tenancy configuration.
70
+ * @author Axel Nana <axel.nana@workbud.com>
71
+ * @param config The tenancy configuration to apply.
72
+ */
73
+ export declare const configure: (config: TenancyConfig) => void;
74
+ /**
75
+ * Gets the current global tenancy configuration.
76
+ * @author Axel Nana <axel.nana@workbud.com>
77
+ * @returns The current tenancy configuration.
78
+ */
79
+ export declare const getConfig: () => TenancyConfig;
80
+ /**
81
+ * Gets the current tenant from the async context.
82
+ * @author Axel Nana <axel.nana@workbud.com>
83
+ * @returns The current tenant identifier, or `null` if not set.
84
+ */
85
+ export declare const getCurrentTenant: () => string | null;
86
+ /**
87
+ * Runs a callback within the context of a specific tenant.
88
+ * @author Axel Nana <axel.nana@workbud.com>
89
+ * @param tenant The tenant identifier to set.
90
+ * @param callback The function to execute within the tenant context.
91
+ * @returns The return value of the callback.
92
+ */
93
+ export declare const withTenant: <T>(tenant: string, callback: () => T) => T;
94
+ /**
95
+ * Gets the Drizzle `PgSchema` for the given tenant.
96
+ * Creates and registers the schema if it doesn't exist yet.
97
+ * @author Axel Nana <axel.nana@workbud.com>
98
+ * @param tenant The name of the tenant.
99
+ * @returns The Postgres schema for the tenant.
100
+ */
101
+ export declare const getTenantSchema: (tenant: string) => PgSchema;
102
+ /**
103
+ * Registers a new Postgres schema for a tenant.
104
+ * If the schema is already registered, returns the existing one.
105
+ * @author Axel Nana <axel.nana@workbud.com>
106
+ * @param tenant The name of the tenant.
107
+ * @returns The newly created or existing tenant schema.
108
+ */
109
+ export declare const registerTenantSchema: (tenant: string) => PgSchema;
110
+ /**
111
+ * Wraps a table definition within a tenant's Postgres schema.
112
+ * For the `public` tenant, returns a standard `pgTable`.
113
+ * Results are cached for performance.
114
+ * @author Axel Nana <axel.nana@workbud.com>
115
+ * @param tenant The tenant identifier.
116
+ * @param tableName The name of the table.
117
+ * @param columns The table columns definition.
118
+ * @returns A `PgTable` scoped to the tenant's schema.
119
+ */
120
+ export declare const wrapTenantSchema: <TTableName extends string, TColumnsMap extends Record<string, PgColumnBuilderBase>>(tenant: string, tableName: TTableName, columns: TColumnsMap) => PgTable;
121
+ /**
122
+ * Execute a callback within a transaction that has the tenant context set
123
+ * via PostgreSQL session variable.
124
+ *
125
+ * Uses `set_config(varName, value, true)` which:
126
+ * - Sets the variable value as a proper GUC parameter (not an identifier)
127
+ * - The `true` flag makes it transaction-local (reverts on commit/rollback)
128
+ *
129
+ * The transaction handle (`tx`) is passed to the callback so all
130
+ * queries execute within the same transaction where `set_config` was called.
131
+ *
132
+ * @author Axel Nana <axel.nana@workbud.com>
133
+ * @param tenant The tenant identifier, or `null` for the public tenant.
134
+ * @param callback A function receiving the transaction handle.
135
+ * @param connectionName The database connection to use.
136
+ * @returns The return value of the callback.
137
+ */
138
+ export declare const withRLS: <T>(tenant: string | null, callback: (tx: PgTransaction<any, any, any>) => Promise<T>, connectionName?: string) => Promise<T>;
139
+ /**
140
+ * Sets the tenant for the current database connection (session-level, not transaction-local).
141
+ * Useful for long-lived connections or connection pools.
142
+ * @author Axel Nana <axel.nana@workbud.com>
143
+ * @param tenant The tenant identifier, or `null` for the public tenant.
144
+ * @param connectionName The database connection to use.
145
+ */
146
+ export declare const setConnectionTenant: (tenant: string | null, connectionName?: string) => Promise<void>;
147
+ /**
148
+ * Reads the current tenant from the PostgreSQL session variable.
149
+ * @author Axel Nana <axel.nana@workbud.com>
150
+ * @param connectionName The database connection to use.
151
+ * @returns The current tenant identifier, or `null` if not set.
152
+ */
153
+ export declare const getSessionTenant: (connectionName?: string) => Promise<string | null>;
154
+ /**
155
+ * Create an RLS policy for tenant isolation.
156
+ *
157
+ * Always specifies `for` and `to` for deterministic behavior:
158
+ * - `for: 'all'` — policy applies to SELECT, INSERT, UPDATE, DELETE
159
+ * - `to: 'public'` — policy applies to all roles
160
+ *
161
+ * @author Axel Nana <axel.nana@workbud.com>
162
+ * @param tableName The name of the table to create the policy for.
163
+ * @param columnName The column that holds the tenant identifier. Defaults to the global config.
164
+ * @param policyName Custom name for the policy. Defaults to `{tableName}_tenant_isolation`.
165
+ * @returns A Drizzle `pgPolicy` definition.
166
+ */
167
+ export declare const createRLSPolicy: (tableName: string, columnName?: string, policyName?: string) => import("drizzle-orm/pg-core").PgPolicy;
168
+ /**
169
+ * Base class for tenant-aware middleware.
170
+ *
171
+ * The middleware extracts the tenant identifier from an HTTP header and
172
+ * sets `ctx.tenant`. For RLS mode, route handlers should use `withRLS()`
173
+ * to wrap their queries in a tenant-scoped transaction.
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * class OrganizationMiddleware extends TenantMiddleware {
178
+ * protected header = 'x-organization-id';
179
+ * protected allowPublic = false;
180
+ *
181
+ * protected async validateTenant(orgId: string): Promise<boolean> {
182
+ * return Organization.exists(orgId);
183
+ * }
184
+ * }
185
+ * ```
186
+ *
187
+ * @author Axel Nana <axel.nana@workbud.com>
188
+ */
189
+ export declare abstract class TenantMiddleware extends Middleware {
190
+ /**
191
+ * The HTTP header name from which the tenant ID is read.
192
+ */
193
+ protected header: string;
194
+ /**
195
+ * Whether requests without a tenant ID are allowed (defaulting to 'public').
196
+ */
197
+ protected allowPublic: boolean;
198
+ /**
199
+ * The database connection name used for tenant operations.
200
+ */
201
+ protected connectionName: string;
202
+ /**
203
+ * Validates that a tenant identifier is legitimate.
204
+ * Override this to check against your data store.
205
+ * @author Axel Nana <axel.nana@workbud.com>
206
+ * @param _tenant The tenant identifier to validate.
207
+ * @returns `true` if the tenant is valid, `false` otherwise.
208
+ */
209
+ protected validateTenant(_tenant: string): Promise<boolean>;
210
+ /**
211
+ * Hook called after a tenant has been successfully resolved.
212
+ * @author Axel Nana <axel.nana@workbud.com>
213
+ * @param _tenant The resolved tenant identifier.
214
+ * @param _ctx The request context.
215
+ */
216
+ protected onTenantResolved(_tenant: string, _ctx: Context): Promise<void>;
217
+ /**
218
+ * Called when the tenant cannot be determined or is invalid.
219
+ * Throws an appropriate error by default.
220
+ * @author Axel Nana <axel.nana@workbud.com>
221
+ * @param reason Whether the tenant is 'missing' or 'invalid'.
222
+ * @param _ctx The request context.
223
+ */
224
+ protected onTenantError(reason: 'missing' | 'invalid', _ctx: Context): never;
225
+ /**
226
+ * Extracts and validates the tenant from the incoming request.
227
+ * @author Axel Nana <axel.nana@workbud.com>
228
+ * @param ctx The request context.
229
+ */
230
+ onRequest(ctx: Context): Promise<any>;
231
+ }
232
+ /**
233
+ * A simple tenant middleware that allows public access by default.
234
+ * @author Axel Nana <axel.nana@workbud.com>
235
+ */
236
+ export declare class SimpleTenantMiddleware extends TenantMiddleware {
237
+ }
238
+ /**
239
+ * A strict tenant middleware that rejects requests without a tenant ID.
240
+ * @author Axel Nana <axel.nana@workbud.com>
241
+ */
242
+ export declare class StrictTenantMiddleware extends TenantMiddleware {
243
+ protected allowPublic: boolean;
244
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elysiumjs/core",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "A super-charged web framework for Bun, built on top of ElysiaJS.",
6
6
  "private": false,
@@ -16,6 +16,7 @@
16
16
  "build:js": "bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify",
17
17
  "build:ts": "tsc",
18
18
  "build": "bun run clean && bun run build:js && bun run build:ts",
19
+ "typecheck": "tsc --noEmit",
19
20
  "prepublishOnly": "bun run build"
20
21
  },
21
22
  "keywords": [
@@ -28,24 +29,16 @@
28
29
  "module": "dist/index.js",
29
30
  "type": "module",
30
31
  "types": "dist/index.d.ts",
32
+ "sideEffects": ["./src/index.ts"],
31
33
  "dependencies": {
32
- "@elysiajs/swagger": "^1.3.0",
33
- "cache-manager": "^6.4.3",
34
- "cacheable": "^1.9.0",
35
- "drizzle-orm": "^0.44.2",
36
- "elysia": "latest",
37
- "radash": "^12.1.0",
38
- "reflect-metadata": "^0.2.2",
39
- "wampy": "^7.1.1"
40
- },
41
- "devDependencies": {
42
- "@ianvs/prettier-plugin-sort-imports": "^4.4.2",
43
- "@types/bun": "latest",
44
- "rimraf": "^6.0.1",
45
- "type-fest": "^4.41.0"
46
- },
47
- "peerDependencies": {
48
- "typescript": "^5.8.3"
34
+ "@elysiajs/swagger": "^1.3.1",
35
+ "cache-manager": "^7.2.3",
36
+ "cacheable": "^2.0.3",
37
+ "drizzle-orm": "^0.44.6",
38
+ "elysia": "^1.4.9",
39
+ "radash": "catalog:",
40
+ "reflect-metadata": "catalog:",
41
+ "wampy": "^7.2.0"
49
42
  },
50
43
  "trustedDependencies": [
51
44
  "es5-ext"
package/tsconfig.json CHANGED
@@ -1,48 +1,12 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
1
  {
2
+ "extends": "../../tsconfig.base.json",
15
3
  "compilerOptions": {
16
- // Environment setup & latest features
17
- "target": "ESNext",
18
- "module": "ESNext",
19
- "moduleDetection": "force",
20
- "allowJs": true,
21
- "types": ["bun-types", "type-fest"],
22
- "experimentalDecorators": true,
23
- "emitDecoratorMetadata": true,
24
- // Compiler setup
25
4
  "outDir": "dist",
26
5
  "declaration": true,
27
6
  "emitDeclarationOnly": true,
28
7
  "declarationMap": false,
29
8
  "declarationDir": "dist",
30
- "rootDir": "src",
31
- // Bundler mode
32
- "moduleResolution": "bundler",
33
- "allowImportingTsExtensions": true,
34
- "verbatimModuleSyntax": true,
35
- // Best practices
36
- "strict": true,
37
- "skipLibCheck": true,
38
- "noFallthroughCasesInSwitch": true,
39
- "noUncheckedIndexedAccess": false,
40
- "forceConsistentCasingInFileNames": true,
41
- // Some stricter flags (disabled by default)
42
- "noUnusedLocals": true,
43
- "noUnusedParameters": true,
44
- "noPropertyAccessFromIndexSignature": false
9
+ "rootDir": "src"
45
10
  },
46
- "exclude": ["node_modules", "dist"],
47
11
  "include": ["src"]
48
12
  }
package/dist/macros.d.ts DELETED
@@ -1,8 +0,0 @@
1
- declare global {
2
- /**
3
- * A macro indicating if the code is running from a build.
4
- * @author Axel Nana <axel.nana@workbud.com>
5
- */
6
- var ELYSIUM_BUILD: boolean;
7
- }
8
- export {};