@getstrata/core 0.7.4 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ - First stable release of the public `@getstrata/core` API.
6
+
7
+ ## 0.7.5
8
+
9
+ - `TENANCY_DRIVER=column`: tenant ALS without Postgres `SET LOCAL` / `set_config`. `isRlsTenancy()` is the RLS-only check.
10
+ - `AuthUserRecord` may include `name` and optional MFA columns used by generated starters.
11
+
3
12
  ## 0.7.4
4
13
 
5
14
  - Default database pool and query handles live on `globalThis`, so the published `@getstrata/core` bundle and `src/core` share one pool in the same process.
package/README.md CHANGED
@@ -23,7 +23,7 @@ import { EtaViewEngine } from "@getstrata/core/view";
23
23
 
24
24
  `.eta` files are **HTML + Eta tags** (`<% %>`, `<%= %>`, `<%~ include() %>`), not another template language. Class/attribute shorthand such as `section.section` or `a href=` fails at render time with the template name.
25
25
 
26
- **Dependency:** `eta` is a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database **engine** is your choice. HiroApp uses **Bun's built-in `Bun.sql`** (Postgres). Create and bind it with `createBunSqlPool()` / `bindBunSql()`, or call `bindDatabaseConnection()` yourself. Extra engines register with `registerNamedConnection` (`database/namedConnections`, `database/sqliteConnection`, `database/mysqlConnection`).
26
+ **Dependency:** `eta` is a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database **engine** is your choice. Generated HiroApp uses **Bun's built-in `Bun.sql`** (Postgres): `createBunSqlPool()`, then `registerDefaultDatabasePool()` and `bindDatabaseConnection()`. `bindBunSql()` is a helper that registers both. Extra engines register with `registerNamedConnection` (`database/namedConnections`, `database/sqliteConnection`, `database/mysqlConnection`).
27
27
 
28
28
  `orderBy` accepts `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`. `{ ilike }` uses the value as-is. Pass `%term%` yourself.
29
29
 
@@ -33,7 +33,7 @@ import { EtaViewEngine } from "@getstrata/core/view";
33
33
  - `createFailedJobService`, `FailedJobService.delete()`: failed job persistence and cleanup
34
34
  - `runQueueJob`, `jobRegistry`: dispatch retried jobs from admin UIs
35
35
 
36
- HiroApp uses failed-job recovery in the staff dashboard. You do not have to use the admin registry.
36
+ Core ships failed-job helpers and an optional admin resource registry. Generated HiroApp does not include an admin dashboard. You do not have to use the registry.
37
37
 
38
38
  ## Build and verify (monorepo root)
39
39
 
@@ -65,7 +65,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
65
65
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
66
66
 
67
67
  1. Add `NPM_TOKEN` to GitHub repository secrets.
68
- 2. Tag a release: `git tag v0.7.4 && git push origin v0.7.4`
68
+ 2. Tag a release: `git tag v1.0.0 && git push origin v1.0.0`
69
69
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
70
70
 
71
71
  See [docs/PACKAGING.md](../../docs/PACKAGING.md).
@@ -1,10 +1,14 @@
1
1
  import type { AuthUser } from "../auth/authContext";
2
2
  interface AuthUserRecord {
3
3
  id: number;
4
+ name?: string | null;
4
5
  email?: string | null;
5
6
  role: string;
6
7
  email_verified_at?: Date | string | null;
7
8
  session_valid_after?: Date | string | null;
9
+ mfa_enabled?: boolean;
10
+ mfa_secret?: string | null;
11
+ mfa_recovery_codes?: string | null;
8
12
  }
9
13
  interface AuthUserDirectory {
10
14
  resolveUserFromToken(token: string): Promise<AuthUser | null>;
@@ -1,5 +1,7 @@
1
- type TenancyDriver = "rls" | "none";
1
+ type TenancyDriver = "rls" | "column" | "none";
2
2
  declare function readTenancyDriver(env?: Record<string, string | undefined>): TenancyDriver;
3
3
  declare function isTenancyEnabled(env?: Record<string, string | undefined>): boolean;
4
+ /** Postgres `SET LOCAL` / `set_config` for row-level security. SQLite and MySQL cannot do this. */
5
+ declare function isRlsTenancy(env?: Record<string, string | undefined>): boolean;
4
6
  export type { TenancyDriver };
5
- export { isTenancyEnabled, readTenancyDriver };
7
+ export { isRlsTenancy, isTenancyEnabled, readTenancyDriver };
@@ -189,7 +189,29 @@ async function safeFetch(input, init = {}, options = {}) {
189
189
 
190
190
  // ../../src/core/tenant/databaseTenantContext.ts
191
191
  import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
192
+
193
+ // ../../src/core/tenant/tenancyConfig.ts
194
+ function readTenancyDriver(env = process.env) {
195
+ if (env.TENANCY_DRIVER === "none") {
196
+ return "none";
197
+ }
198
+ if (env.TENANCY_DRIVER === "column") {
199
+ return "column";
200
+ }
201
+ return "rls";
202
+ }
203
+ function isTenancyEnabled(env = process.env) {
204
+ return readTenancyDriver(env) !== "none";
205
+ }
206
+ function isRlsTenancy(env = process.env) {
207
+ return readTenancyDriver(env) === "rls";
208
+ }
209
+
210
+ // ../../src/core/tenant/databaseTenantContext.ts
192
211
  async function runWithMigrationBypass(callback) {
212
+ if (!isRlsTenancy()) {
213
+ return await callback();
214
+ }
193
215
  await db`SELECT set_config('app.bypass_rls', 'true', false)`;
194
216
  try {
195
217
  return await callback();
@@ -79,11 +79,20 @@ import { repositoryConnection as db } from "@getstrata/core/database/repositoryC
79
79
 
80
80
  // ../../src/core/tenant/tenancyConfig.ts
81
81
  function readTenancyDriver(env = process.env) {
82
- return env.TENANCY_DRIVER === "none" ? "none" : "rls";
82
+ if (env.TENANCY_DRIVER === "none") {
83
+ return "none";
84
+ }
85
+ if (env.TENANCY_DRIVER === "column") {
86
+ return "column";
87
+ }
88
+ return "rls";
83
89
  }
84
90
  function isTenancyEnabled(env = process.env) {
85
91
  return readTenancyDriver(env) !== "none";
86
92
  }
93
+ function isRlsTenancy(env = process.env) {
94
+ return readTenancyDriver(env) === "rls";
95
+ }
87
96
 
88
97
  // ../../src/core/tenant/resolveTenant.ts
89
98
  async function resolveTenant(tenantId) {
@@ -123,12 +132,12 @@ async function applyTenantContextToTransaction(transaction, tenantId) {
123
132
  await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
124
133
  }
125
134
  async function runWithTenantDatabase(tenant, callback) {
126
- if (!isTenancyEnabled()) {
135
+ if (!isTenancyEnabled() || !isRlsTenancy()) {
127
136
  return await runWithTenant(tenant, callback);
128
137
  }
129
138
  if (hasActiveDatabaseConnection()) {
130
- const activeConnection2 = getActiveDatabaseConnection(getDefaultDatabasePool());
131
- await applyTenantContextToTransaction(activeConnection2, tenant.id);
139
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
140
+ await applyTenantContextToTransaction(activeConnection, tenant.id);
132
141
  return await runWithTenant(tenant, callback);
133
142
  }
134
143
  return await getDefaultDatabasePool().begin(async (transaction) => {
@@ -127,8 +127,8 @@ function readSession(request) {
127
127
  }
128
128
  const parts = cookieValue.split(".");
129
129
  if (parts.length === 4) {
130
- const [userIdRaw2, issuedAtRaw2, ttlRaw, cookieSignature2] = parts;
131
- return readSignedSession(String(userIdRaw2), String(issuedAtRaw2), cookieSignature2, Number.parseInt(String(ttlRaw), 10), true);
130
+ const [userIdRaw, issuedAtRaw, ttlRaw, cookieSignature] = parts;
131
+ return readSignedSession(String(userIdRaw), String(issuedAtRaw), cookieSignature, Number.parseInt(String(ttlRaw), 10), true);
132
132
  }
133
133
  if (parts.length !== 3) {
134
134
  return null;
@@ -194,8 +194,8 @@ function readSession(request) {
194
194
  }
195
195
  const parts = cookieValue.split(".");
196
196
  if (parts.length === 4) {
197
- const [userIdRaw2, issuedAtRaw2, ttlRaw, cookieSignature2] = parts;
198
- return readSignedSession(String(userIdRaw2), String(issuedAtRaw2), cookieSignature2, Number.parseInt(String(ttlRaw), 10), true);
197
+ const [userIdRaw, issuedAtRaw, ttlRaw, cookieSignature] = parts;
198
+ return readSignedSession(String(userIdRaw), String(issuedAtRaw), cookieSignature, Number.parseInt(String(ttlRaw), 10), true);
199
199
  }
200
200
  if (parts.length !== 3) {
201
201
  return null;
@@ -298,8 +298,8 @@ function buildSelectList(table, select, params = []) {
298
298
  }
299
299
  return select.map((item) => {
300
300
  if (item.kind === "column") {
301
- const column2 = qualifyColumn(item.table, item.column);
302
- return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
301
+ const column = qualifyColumn(item.table, item.column);
302
+ return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
303
303
  }
304
304
  if (item.kind === "literalText") {
305
305
  return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
@@ -1919,8 +1919,8 @@ class Model {
1919
1919
  }
1920
1920
  if (updating) {
1921
1921
  const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
1922
- const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
1923
- this.attributes = ModelClass.hydrateAttributes(record2);
1922
+ const record = await this.repository.updateByIdOrThrow(this.id, changes);
1923
+ this.attributes = ModelClass.hydrateAttributes(record);
1924
1924
  await runObservers(this, "updated");
1925
1925
  await runObservers(this, "saved");
1926
1926
  return this;
@@ -275,8 +275,8 @@ function buildSelectList(table, select, params = []) {
275
275
  }
276
276
  return select.map((item) => {
277
277
  if (item.kind === "column") {
278
- const column2 = qualifyColumn(item.table, item.column);
279
- return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
278
+ const column = qualifyColumn(item.table, item.column);
279
+ return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
280
280
  }
281
281
  if (item.kind === "literalText") {
282
282
  return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
@@ -275,8 +275,8 @@ function buildSelectList(table, select, params = []) {
275
275
  }
276
276
  return select.map((item) => {
277
277
  if (item.kind === "column") {
278
- const column2 = qualifyColumn(item.table, item.column);
279
- return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
278
+ const column = qualifyColumn(item.table, item.column);
279
+ return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
280
280
  }
281
281
  if (item.kind === "literalText") {
282
282
  return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
@@ -925,50 +925,50 @@ class RepositoryQuery {
925
925
  }
926
926
  async hydrateEagerLoad(rows, result, load) {
927
927
  if (load.kind === "hasMany") {
928
- const relation2 = load.relation;
929
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
928
+ const relation = load.relation;
929
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation, load.options);
930
930
  for (const row of result) {
931
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
931
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]) ?? [];
932
932
  }
933
933
  return;
934
934
  }
935
935
  if (load.kind === "morphMany") {
936
- const relation2 = load.relation;
937
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
936
+ const relation = load.relation;
937
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation, load.options);
938
938
  for (const row of result) {
939
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
939
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]) ?? [];
940
940
  }
941
941
  return;
942
942
  }
943
943
  if (load.kind === "morphOne") {
944
- const relation2 = load.relation;
945
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
944
+ const relation = load.relation;
945
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation, load.options);
946
946
  for (const row of result) {
947
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]);
947
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]);
948
948
  }
949
949
  return;
950
950
  }
951
951
  if (load.kind === "hasManyThrough") {
952
- const relation2 = load.relation;
953
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
952
+ const relation = load.relation;
953
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation, load.options);
954
954
  for (const row of result) {
955
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
955
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]) ?? [];
956
956
  }
957
957
  return;
958
958
  }
959
959
  if (load.kind === "belongsToMany") {
960
- const relation2 = load.relation;
961
- const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
960
+ const relation = load.relation;
961
+ const grouped = await this.repository.loadBelongsToManyForParents(rows, relation, load.repository, load.options);
962
962
  for (const row of result) {
963
- row[load.as] = getByRelationKey(grouped2, row[relation2.parentKey]) ?? [];
963
+ row[load.as] = getByRelationKey(grouped, row[relation.parentKey]) ?? [];
964
964
  }
965
965
  return;
966
966
  }
967
967
  if (load.kind === "morphTo") {
968
- const relation2 = load.relation;
969
- const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
968
+ const relation = load.relation;
969
+ const grouped = await this.repository.loadMorphToForChildren(rows, relation, load.morphRepositories ?? new Map, load.options);
970
970
  for (const row of result) {
971
- row[load.as] = getByRelationKey(grouped2, row[relation2.morphIdKey]);
971
+ row[load.as] = getByRelationKey(grouped, row[relation.morphIdKey]);
972
972
  }
973
973
  return;
974
974
  }
@@ -542,8 +542,8 @@ function buildSelectList(table, select, params = []) {
542
542
  }
543
543
  return select.map((item) => {
544
544
  if (item.kind === "column") {
545
- const column2 = qualifyColumn(item.table, item.column);
546
- return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
545
+ const column = qualifyColumn(item.table, item.column);
546
+ return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
547
547
  }
548
548
  if (item.kind === "literalText") {
549
549
  return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
@@ -189,7 +189,29 @@ async function safeFetch(input, init = {}, options = {}) {
189
189
 
190
190
  // ../../src/core/tenant/databaseTenantContext.ts
191
191
  import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
192
+
193
+ // ../../src/core/tenant/tenancyConfig.ts
194
+ function readTenancyDriver(env = process.env) {
195
+ if (env.TENANCY_DRIVER === "none") {
196
+ return "none";
197
+ }
198
+ if (env.TENANCY_DRIVER === "column") {
199
+ return "column";
200
+ }
201
+ return "rls";
202
+ }
203
+ function isTenancyEnabled(env = process.env) {
204
+ return readTenancyDriver(env) !== "none";
205
+ }
206
+ function isRlsTenancy(env = process.env) {
207
+ return readTenancyDriver(env) === "rls";
208
+ }
209
+
210
+ // ../../src/core/tenant/databaseTenantContext.ts
192
211
  async function runWithMigrationBypass(callback) {
212
+ if (!isRlsTenancy()) {
213
+ return await callback();
214
+ }
193
215
  await db`SELECT set_config('app.bypass_rls', 'true', false)`;
194
216
  try {
195
217
  return await callback();
@@ -254,13 +254,13 @@ function renderOpenApiDocument(spec) {
254
254
  return `${JSON.stringify(spec, null, 2)}
255
255
  `;
256
256
  }
257
- function toMethodName(method, path, apiPrefix2) {
258
- const relativePath = path.startsWith(apiPrefix2) ? path.slice(apiPrefix2.length) || "/" : path;
257
+ function toMethodName(method, path, apiPrefix) {
258
+ const relativePath = path.startsWith(apiPrefix) ? path.slice(apiPrefix.length) || "/" : path;
259
259
  const segments = relativePath.replace(/\{|\}/g, "").split("/").filter(Boolean).flatMap((segment) => segment.split("-")).map((segment) => segment.replace(/[^a-zA-Z0-9]/g, "")).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1));
260
260
  return `${method.toLowerCase()}${segments.join("")}`;
261
261
  }
262
- function toRequestPath(path, apiPrefix2) {
263
- return path.startsWith(apiPrefix2) ? path.slice(apiPrefix2.length) || "/" : path;
262
+ function toRequestPath(path, apiPrefix) {
263
+ return path.startsWith(apiPrefix) ? path.slice(apiPrefix.length) || "/" : path;
264
264
  }
265
265
  function renderTypeScriptSdk(spec, prefix = apiPrefix()) {
266
266
  const lines = [
@@ -1,7 +1,29 @@
1
1
  // @bun
2
2
  // ../../src/core/tenant/databaseTenantContext.ts
3
3
  import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
4
+
5
+ // ../../src/core/tenant/tenancyConfig.ts
6
+ function readTenancyDriver(env = process.env) {
7
+ if (env.TENANCY_DRIVER === "none") {
8
+ return "none";
9
+ }
10
+ if (env.TENANCY_DRIVER === "column") {
11
+ return "column";
12
+ }
13
+ return "rls";
14
+ }
15
+ function isTenancyEnabled(env = process.env) {
16
+ return readTenancyDriver(env) !== "none";
17
+ }
18
+ function isRlsTenancy(env = process.env) {
19
+ return readTenancyDriver(env) === "rls";
20
+ }
21
+
22
+ // ../../src/core/tenant/databaseTenantContext.ts
4
23
  async function runWithMigrationBypass(callback) {
24
+ if (!isRlsTenancy()) {
25
+ return await callback();
26
+ }
5
27
  await db`SELECT set_config('app.bypass_rls', 'true', false)`;
6
28
  try {
7
29
  return await callback();
@@ -1,12 +1,22 @@
1
1
  // @bun
2
2
  // ../../src/core/tenant/tenancyConfig.ts
3
3
  function readTenancyDriver(env = process.env) {
4
- return env.TENANCY_DRIVER === "none" ? "none" : "rls";
4
+ if (env.TENANCY_DRIVER === "none") {
5
+ return "none";
6
+ }
7
+ if (env.TENANCY_DRIVER === "column") {
8
+ return "column";
9
+ }
10
+ return "rls";
5
11
  }
6
12
  function isTenancyEnabled(env = process.env) {
7
13
  return readTenancyDriver(env) !== "none";
8
14
  }
15
+ function isRlsTenancy(env = process.env) {
16
+ return readTenancyDriver(env) === "rls";
17
+ }
9
18
  export {
19
+ isRlsTenancy,
10
20
  isTenancyEnabled,
11
21
  readTenancyDriver
12
22
  };
@@ -30,11 +30,20 @@ function hasActiveDatabaseConnection() {
30
30
 
31
31
  // ../../src/core/tenant/tenancyConfig.ts
32
32
  function readTenancyDriver(env = process.env) {
33
- return env.TENANCY_DRIVER === "none" ? "none" : "rls";
33
+ if (env.TENANCY_DRIVER === "none") {
34
+ return "none";
35
+ }
36
+ if (env.TENANCY_DRIVER === "column") {
37
+ return "column";
38
+ }
39
+ return "rls";
34
40
  }
35
41
  function isTenancyEnabled(env = process.env) {
36
42
  return readTenancyDriver(env) !== "none";
37
43
  }
44
+ function isRlsTenancy(env = process.env) {
45
+ return readTenancyDriver(env) === "rls";
46
+ }
38
47
 
39
48
  // ../../src/core/tenant/tenantContext.ts
40
49
  var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
@@ -51,12 +60,12 @@ async function applyTenantContextToTransaction(transaction, tenantId) {
51
60
  await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
52
61
  }
53
62
  async function runWithTenantDatabase(tenant, callback) {
54
- if (!isTenancyEnabled()) {
63
+ if (!isTenancyEnabled() || !isRlsTenancy()) {
55
64
  return await runWithTenant(tenant, callback);
56
65
  }
57
66
  if (hasActiveDatabaseConnection()) {
58
- const activeConnection2 = getActiveDatabaseConnection(getDefaultDatabasePool());
59
- await applyTenantContextToTransaction(activeConnection2, tenant.id);
67
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
68
+ await applyTenantContextToTransaction(activeConnection, tenant.id);
60
69
  return await runWithTenant(tenant, callback);
61
70
  }
62
71
  return await getDefaultDatabasePool().begin(async (transaction) => {
@@ -126,7 +126,7 @@ export { logSecurityEvent } from "../core/security/securityEvents.ts";
126
126
  export type { StorageDriver } from "../core/storage/storage.ts";
127
127
  export { createStorageDriver, LocalStorageDriver, resetDefaultStorage, StorageManager, } from "../core/storage/storage.ts";
128
128
  export type { TenancyDriver } from "../core/tenant/tenancyConfig.ts";
129
- export { isTenancyEnabled, readTenancyDriver } from "../core/tenant/tenancyConfig.ts";
129
+ export { isRlsTenancy, isTenancyEnabled, readTenancyDriver, } from "../core/tenant/tenancyConfig.ts";
130
130
  export type { TenantContext } from "../core/tenant/tenantContext.ts";
131
131
  export { currentTenant, currentTenantId, rateLimitMultiplierForPlan, runWithTenant, } from "../core/tenant/tenantContext.ts";
132
132
  export { isInsideTenantDatabaseScope, runWithTenantDatabase, } from "../core/tenant/tenantDatabaseScope.ts";
package/dist/index.js CHANGED
@@ -326,11 +326,11 @@ function abilityCatalog() {
326
326
 
327
327
  // ../../src/core/auth/guard.ts
328
328
  function devHeaderAbilities(role) {
329
- const catalog2 = abilityCatalog();
329
+ const catalog = abilityCatalog();
330
330
  if (role === "admin") {
331
- return [...catalog2.admin];
331
+ return [...catalog.admin];
332
332
  }
333
- return [...catalog2.member];
333
+ return [...catalog.member];
334
334
  }
335
335
 
336
336
  class GuestGuard {
@@ -1144,7 +1144,7 @@ function resolveScimTenantFromToken(token) {
1144
1144
  function resolveRepositoryConnection() {
1145
1145
  return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
1146
1146
  }
1147
- var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
1147
+ var repositoryConnection = new Proxy(function repositoryConnection() {}, {
1148
1148
  apply(_target, _thisArg, args) {
1149
1149
  return resolveRepositoryConnection()(...args);
1150
1150
  },
@@ -1157,11 +1157,20 @@ var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
1157
1157
 
1158
1158
  // ../../src/core/tenant/tenancyConfig.ts
1159
1159
  function readTenancyDriver(env = process.env) {
1160
- return env.TENANCY_DRIVER === "none" ? "none" : "rls";
1160
+ if (env.TENANCY_DRIVER === "none") {
1161
+ return "none";
1162
+ }
1163
+ if (env.TENANCY_DRIVER === "column") {
1164
+ return "column";
1165
+ }
1166
+ return "rls";
1161
1167
  }
1162
1168
  function isTenancyEnabled(env = process.env) {
1163
1169
  return readTenancyDriver(env) !== "none";
1164
1170
  }
1171
+ function isRlsTenancy(env = process.env) {
1172
+ return readTenancyDriver(env) === "rls";
1173
+ }
1165
1174
 
1166
1175
  // ../../src/core/tenant/resolveTenant.ts
1167
1176
  async function resolveTenant(tenantId) {
@@ -1189,12 +1198,12 @@ async function applyTenantContextToTransaction(transaction, tenantId) {
1189
1198
  await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
1190
1199
  }
1191
1200
  async function runWithTenantDatabase(tenant, callback) {
1192
- if (!isTenancyEnabled()) {
1201
+ if (!isTenancyEnabled() || !isRlsTenancy()) {
1193
1202
  return await runWithTenant(tenant, callback);
1194
1203
  }
1195
1204
  if (hasActiveDatabaseConnection()) {
1196
- const activeConnection2 = getActiveDatabaseConnection(getDefaultDatabasePool());
1197
- await applyTenantContextToTransaction(activeConnection2, tenant.id);
1205
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
1206
+ await applyTenantContextToTransaction(activeConnection, tenant.id);
1198
1207
  return await runWithTenant(tenant, callback);
1199
1208
  }
1200
1209
  return await getDefaultDatabasePool().begin(async (transaction) => {
@@ -2307,8 +2316,8 @@ function buildSelectList(table, select, params = []) {
2307
2316
  }
2308
2317
  return select.map((item) => {
2309
2318
  if (item.kind === "column") {
2310
- const column2 = qualifyColumn(item.table, item.column);
2311
- return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
2319
+ const column = qualifyColumn(item.table, item.column);
2320
+ return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
2312
2321
  }
2313
2322
  if (item.kind === "literalText") {
2314
2323
  return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
@@ -2957,50 +2966,50 @@ class RepositoryQuery {
2957
2966
  }
2958
2967
  async hydrateEagerLoad(rows, result, load) {
2959
2968
  if (load.kind === "hasMany") {
2960
- const relation2 = load.relation;
2961
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
2969
+ const relation = load.relation;
2970
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation, load.options);
2962
2971
  for (const row of result) {
2963
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2972
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]) ?? [];
2964
2973
  }
2965
2974
  return;
2966
2975
  }
2967
2976
  if (load.kind === "morphMany") {
2968
- const relation2 = load.relation;
2969
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
2977
+ const relation = load.relation;
2978
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation, load.options);
2970
2979
  for (const row of result) {
2971
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2980
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]) ?? [];
2972
2981
  }
2973
2982
  return;
2974
2983
  }
2975
2984
  if (load.kind === "morphOne") {
2976
- const relation2 = load.relation;
2977
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
2985
+ const relation = load.relation;
2986
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation, load.options);
2978
2987
  for (const row of result) {
2979
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]);
2988
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]);
2980
2989
  }
2981
2990
  return;
2982
2991
  }
2983
2992
  if (load.kind === "hasManyThrough") {
2984
- const relation2 = load.relation;
2985
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
2993
+ const relation = load.relation;
2994
+ const grouped = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation, load.options);
2986
2995
  for (const row of result) {
2987
- row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2996
+ row[load.as] = getByRelationKey(grouped, row[relation.localKey]) ?? [];
2988
2997
  }
2989
2998
  return;
2990
2999
  }
2991
3000
  if (load.kind === "belongsToMany") {
2992
- const relation2 = load.relation;
2993
- const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
3001
+ const relation = load.relation;
3002
+ const grouped = await this.repository.loadBelongsToManyForParents(rows, relation, load.repository, load.options);
2994
3003
  for (const row of result) {
2995
- row[load.as] = getByRelationKey(grouped2, row[relation2.parentKey]) ?? [];
3004
+ row[load.as] = getByRelationKey(grouped, row[relation.parentKey]) ?? [];
2996
3005
  }
2997
3006
  return;
2998
3007
  }
2999
3008
  if (load.kind === "morphTo") {
3000
- const relation2 = load.relation;
3001
- const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
3009
+ const relation = load.relation;
3010
+ const grouped = await this.repository.loadMorphToForChildren(rows, relation, load.morphRepositories ?? new Map, load.options);
3002
3011
  for (const row of result) {
3003
- row[load.as] = getByRelationKey(grouped2, row[relation2.morphIdKey]);
3012
+ row[load.as] = getByRelationKey(grouped, row[relation.morphIdKey]);
3004
3013
  }
3005
3014
  return;
3006
3015
  }
@@ -4937,8 +4946,8 @@ class Model {
4937
4946
  }
4938
4947
  if (updating) {
4939
4948
  const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
4940
- const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
4941
- this.attributes = ModelClass.hydrateAttributes(record2);
4949
+ const record = await this.repository.updateByIdOrThrow(this.id, changes);
4950
+ this.attributes = ModelClass.hydrateAttributes(record);
4942
4951
  await runObservers(this, "updated");
4943
4952
  await runObservers(this, "saved");
4944
4953
  return this;
@@ -6911,9 +6920,9 @@ class FormRequest {
6911
6920
  }
6912
6921
  }
6913
6922
  // ../../src/core/http/authMiddleware.ts
6914
- function createAuthMiddleware(auth2) {
6923
+ function createAuthMiddleware(auth) {
6915
6924
  return async (request, next) => {
6916
- const user = await auth2.resolve(request);
6925
+ const user = await auth.resolve(request);
6917
6926
  return await runWithAuthUser(user, async () => {
6918
6927
  const response = await next();
6919
6928
  if (user) {
@@ -6930,9 +6939,9 @@ function createAuthMiddleware(auth2) {
6930
6939
  };
6931
6940
  }
6932
6941
  // ../../src/core/http/authorizeMiddleware.ts
6933
- function createAuthorizeMiddleware(gate, auth2, resource, action) {
6942
+ function createAuthorizeMiddleware(gate, auth, resource, action) {
6934
6943
  return async (request, next) => {
6935
- const user = currentAuthUser() ?? await auth2.resolve(request);
6944
+ const user = currentAuthUser() ?? await auth.resolve(request);
6936
6945
  if (!gate.allows(resource, action, user)) {
6937
6946
  const error = new ForbiddenError;
6938
6947
  return Response.json({ error: error.message }, { status: error.status });
@@ -7106,9 +7115,9 @@ async function parseMultipartUpload(request, fieldName = "file") {
7106
7115
  };
7107
7116
  }
7108
7117
  // ../../src/core/http/requireAuthMiddleware.ts
7109
- function createRequireAuthMiddleware(auth2) {
7118
+ function createRequireAuthMiddleware(auth) {
7110
7119
  return async (request, next) => {
7111
- if (!await auth2.check(request)) {
7120
+ if (!await auth.check(request)) {
7112
7121
  const error = new UnauthorizedError;
7113
7122
  return Response.json({ error: error.message }, { status: error.status });
7114
7123
  }
@@ -7504,8 +7513,8 @@ function securedBindRouteModel(param, resolver, authorization, handler) {
7504
7513
  const id = parsePositiveIntParam(String(request.params[param]), String(param));
7505
7514
  const model = requireResolvedModel(await resolver(id, request));
7506
7515
  const gate = resolveApplicationPolicyGate();
7507
- const auth2 = resolveApplicationAuth();
7508
- const user = currentAuthUser() ?? await auth2.resolve(request);
7516
+ const auth = resolveApplicationAuth();
7517
+ const user = currentAuthUser() ?? await auth.resolve(request);
7509
7518
  gate.authorize(authorization.resource, authorization.action, user, model);
7510
7519
  if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
7511
7520
  assertIfMatch(request, etagFromResource(model), {
@@ -7527,8 +7536,8 @@ function securedBindRouteModelByKey(param, resolver, authorization, handler) {
7527
7536
  }
7528
7537
  const model = requireResolvedModel(await resolver(key, request));
7529
7538
  const gate = resolveApplicationPolicyGate();
7530
- const auth2 = resolveApplicationAuth();
7531
- const user = currentAuthUser() ?? await auth2.resolve(request);
7539
+ const auth = resolveApplicationAuth();
7540
+ const user = currentAuthUser() ?? await auth.resolve(request);
7532
7541
  gate.authorize(authorization.resource, authorization.action, user, model);
7533
7542
  if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
7534
7543
  assertIfMatch(request, etagFromResource(model), {
@@ -7850,9 +7859,9 @@ function createIntendedUrlCookieFromRequest(request) {
7850
7859
  }
7851
7860
 
7852
7861
  // ../../src/core/http/requireWebAuthMiddleware.ts
7853
- function createRequireWebAuthMiddleware(auth2) {
7862
+ function createRequireWebAuthMiddleware(auth) {
7854
7863
  return async (request, next) => {
7855
- const user = await auth2.resolve(request);
7864
+ const user = await auth.resolve(request);
7856
7865
  if (user) {
7857
7866
  return await runWithAuthUser(user, () => next());
7858
7867
  }
@@ -8281,15 +8290,15 @@ function buildMarkdownMailMessage(input) {
8281
8290
  html: rendered.html
8282
8291
  };
8283
8292
  }
8284
- async function sendMarkdownMail(mailer2, input) {
8285
- await mailer2.send(buildMarkdownMailMessage(input));
8293
+ async function sendMarkdownMail(mailer, input) {
8294
+ await mailer.send(buildMarkdownMailMessage(input));
8286
8295
  }
8287
8296
  // ../../src/core/notifications/dispatcher.ts
8288
8297
  class NotificationDispatcher {
8289
8298
  mailer;
8290
8299
  databaseStore;
8291
- constructor(mailer2, databaseStore = null) {
8292
- this.mailer = mailer2;
8300
+ constructor(mailer, databaseStore = null) {
8301
+ this.mailer = mailer;
8293
8302
  this.databaseStore = databaseStore;
8294
8303
  }
8295
8304
  async send(notifiable, notification) {
@@ -8341,8 +8350,8 @@ class NotificationDispatcher {
8341
8350
  });
8342
8351
  }
8343
8352
  }
8344
- function createNotificationDispatcher(mailer2, databaseStore) {
8345
- return new NotificationDispatcher(mailer2, databaseStore ?? null);
8353
+ function createNotificationDispatcher(mailer, databaseStore) {
8354
+ return new NotificationDispatcher(mailer, databaseStore ?? null);
8346
8355
  }
8347
8356
  // ../../src/core/notifications/notification.ts
8348
8357
  class Notification {
@@ -8472,9 +8481,9 @@ function readSharedJobRegistry() {
8472
8481
  if (globalRegistry) {
8473
8482
  return globalRegistry;
8474
8483
  }
8475
- const registry2 = new JobRegistry;
8476
- globalThis[JOB_REGISTRY_KEY] = registry2;
8477
- return registry2;
8484
+ const registry = new JobRegistry;
8485
+ globalThis[JOB_REGISTRY_KEY] = registry;
8486
+ return registry;
8478
8487
  }
8479
8488
  var jobRegistry = readSharedJobRegistry();
8480
8489
 
@@ -8829,6 +8838,9 @@ import { createHash } from "crypto";
8829
8838
 
8830
8839
  // ../../src/core/tenant/databaseTenantContext.ts
8831
8840
  async function runWithMigrationBypass(callback) {
8841
+ if (!isRlsTenancy()) {
8842
+ return await callback();
8843
+ }
8832
8844
  await repositoryConnection`SELECT set_config('app.bypass_rls', 'true', false)`;
8833
8845
  try {
8834
8846
  return await callback();
@@ -9286,9 +9298,9 @@ class EtaViewEngine {
9286
9298
  autoTrim: false
9287
9299
  });
9288
9300
  this.resolveLayoutData = resolveLayoutData;
9289
- const readFile2 = this.eta.readFile?.bind(this.eta);
9301
+ const readFile = this.eta.readFile?.bind(this.eta);
9290
9302
  this.eta.readFile = (path) => {
9291
- const source = readFile2 ? readFile2(path) : "";
9303
+ const source = readFile ? readFile(path) : "";
9292
9304
  assertEtaHtmlSource(relative(viewsDirectory, path) || path, source);
9293
9305
  return source;
9294
9306
  };
@@ -9561,6 +9573,7 @@ export {
9561
9573
  isHttpErrorLike,
9562
9574
  isInsideTenantDatabaseScope,
9563
9575
  isPublicReadsEnabled,
9576
+ isRlsTenancy,
9564
9577
  isTenancyEnabled,
9565
9578
  jobRegistry,
9566
9579
  jsonResponse2 as jsonResponse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.7.4",
3
+ "version": "1.0.0",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",