@getstrata/core 0.5.13 → 0.5.15

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.
Files changed (46) hide show
  1. package/README.md +16 -6
  2. package/dist/bootstrap/contracts.d.ts +2 -0
  3. package/dist/bootstrap/httpKernel.d.ts +1 -2
  4. package/dist/core/admin/registry.d.ts +1 -0
  5. package/dist/core/auth/membershipService.d.ts +5 -1
  6. package/dist/core/database/baseRepository.d.ts +6 -1
  7. package/dist/core/database/connectionContext.d.ts +2 -1
  8. package/dist/core/database/defaultConnection.d.ts +6 -0
  9. package/dist/core/database/queryProxy.d.ts +3 -0
  10. package/dist/core/database/repositoryConnection.d.ts +3 -3
  11. package/dist/core/jobs/dispatchWebhookJob.d.ts +0 -1
  12. package/dist/core/security/safeFetch.d.ts +2 -0
  13. package/dist/core/security/safeUrl.d.ts +16 -1
  14. package/dist/core/storage/storage.d.ts +6 -3
  15. package/dist/core/tenant/tenantDatabaseScope.d.ts +2 -1
  16. package/dist/db/connection/index.d.ts +1 -1
  17. package/dist/entries/auth/accessControl.js +1 -113
  18. package/dist/entries/auth/authContext.js +1 -15
  19. package/dist/entries/auth/guard.js +1 -3044
  20. package/dist/entries/auth/membershipContext.js +1 -276
  21. package/dist/entries/auth/membershipScope.js +1 -390
  22. package/dist/entries/auth/membershipService.js +1 -480
  23. package/dist/entries/auth/policy.js +1 -134
  24. package/dist/entries/database.js +1 -2398
  25. package/dist/entries/http/csrfToken.js +0 -6
  26. package/dist/entries/http/middleware.js +1 -68
  27. package/dist/entries/http/requestMetaContext.js +1 -18
  28. package/dist/entries/http/webErrorResponse.js +94 -563
  29. package/dist/entries/http/webFormRequest.js +0 -131
  30. package/dist/entries/http.js +1 -4091
  31. package/dist/entries/jobs/dispatchWebhookJob.js +109 -102
  32. package/dist/entries/queue/createAppQueue.js +111 -631
  33. package/dist/entries/queue/jobRunner.js +0 -3
  34. package/dist/entries/queue/publicQueue.js +45 -546
  35. package/dist/entries/queue/queueMetrics.js +111 -631
  36. package/dist/entries/security/safeUrl.js +29 -0
  37. package/dist/entries/security/securityEvents.js +1 -41
  38. package/dist/entries/storage/storage.js +14 -4
  39. package/dist/entries/tenant/tenantContext.js +1 -30
  40. package/dist/entries/tenant/tenantMiddleware.js +1 -312
  41. package/dist/entries/tracing/traceContext.js +1 -15
  42. package/dist/entries/view.js +94 -563
  43. package/dist/framework/public-api.d.ts +36 -4
  44. package/dist/index.js +2270 -1666
  45. package/dist/modules/user/repository.d.ts +1 -0
  46. package/package.json +6 -5
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Stable **Strata** framework surface for application modules.
4
4
 
5
5
  **Source:** `src/framework/public-api.ts` (monorepo)
6
- **Repository:** [EyK-26/strata](https://github.com/EyK-26/strata) directory `packages/strata-core`
6
+ **Repository:** [EyK-26/strata](https://github.com/EyK-26/strata), directory `packages/strata-core`
7
7
 
8
8
  WorkHub is the reference application built on Strata; import the framework from this package in your own modules.
9
9
 
@@ -15,21 +15,31 @@ Set `DATABASE_URL` before importing (connection is created lazily on first query
15
15
  process.env.DATABASE_URL ??= "postgresql://postgres:postgres@localhost:5432/myapp";
16
16
 
17
17
  import {
18
+ AdminResourceRegistry,
18
19
  BaseRepository,
19
20
  EtaViewEngine,
20
21
  FormRequest,
21
22
  Policy,
23
+ formatAdminValue,
22
24
  mailer,
23
25
  storage,
24
26
  withErrorHandling,
25
27
  } from "@getstrata/core";
26
28
  ```
27
29
 
28
- **Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core` apps do not need to list it separately. The database driver is your app's choice WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; bind it with `bindDatabaseConnection()`.
30
+ **Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database driver is your app's choice. WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; bind it with `bindDatabaseConnection()`.
29
31
 
30
- `orderBy` accepts explicit `{ column, direction }` objects or Laravel-style shorthand `{ published_at: "desc" }`.
32
+ `orderBy` accepts explicit `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`.
31
33
 
32
- ## Build & verify (monorepo root)
34
+ ## Admin and queue helpers
35
+
36
+ Exports for admin dashboards and queue recovery:
37
+
38
+ - `AdminResourceRegistry`, `formatAdminValue`: read-only resource browsers
39
+ - `createFailedJobService`, `FailedJobService.delete()`: failed job persistence and cleanup
40
+ - `runQueueJob`, `jobRegistry`: dispatch retried jobs from admin UIs
41
+
42
+ ## Build and verify (monorepo root)
33
43
 
34
44
  ```bash
35
45
  bun run build:framework
@@ -41,9 +51,9 @@ bun run verify:framework # build + public API tests
41
51
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
42
52
 
43
53
  1. Add `NPM_TOKEN` to GitHub repository secrets.
44
- 2. Tag a release: `git tag v0.2.0 && git push origin v0.2.0`
54
+ 2. Tag a release: `git tag v0.5.13 && git push origin v0.5.13`
45
55
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
46
56
 
47
- Previously published as `@eyk-workhub/framework@0.1.0` deprecated in favor of this package.
57
+ Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
48
58
 
49
59
  See [docs/PACKAGING.md](../../docs/PACKAGING.md) for boundaries and future extraction.
@@ -1,3 +1,4 @@
1
+ import type { StorageManager } from "../core/storage/storage";
1
2
  import type { CacheLike } from "../types/services";
2
3
  type CachedJson = <T>(cacheKey: string, loader: () => Promise<T>, tags?: string[], request?: Request) => Promise<Response>;
3
4
  type AppRouteMap = Record<string, any>;
@@ -23,6 +24,7 @@ declare class ConfigStore {
23
24
  interface AppDependencies {
24
25
  container: ServiceContainer;
25
26
  cache: CacheLike;
27
+ storage: StorageManager;
26
28
  }
27
29
  type MutableAppDependencies = Partial<Omit<AppDependencies, "container">> & Pick<AppDependencies, "container">;
28
30
  interface ProviderContext {
@@ -1,5 +1,4 @@
1
- import type { Policy } from "../core/auth/policy";
2
- import { type Middleware, type RouteHandler } from "../core/http/middleware";
1
+ import { type Middleware, type Policy, type RouteHandler } from "@getstrata/core";
3
2
  import type { AppDependencies } from "./contracts";
4
3
  type MiddlewareGroupName = "api" | "authenticated" | "web";
5
4
  declare class HttpKernel {
@@ -1,6 +1,7 @@
1
1
  import type { AdminResource, AdminResourceDefinition } from "./types.ts";
2
2
  declare class AdminResourceRegistry {
3
3
  private readonly resources;
4
+ constructor();
4
5
  register<TEntity extends object>(resource: AdminResource<TEntity>): void;
5
6
  get(name: string): AdminResource<object> | undefined;
6
7
  list(): AdminResourceDefinition[];
@@ -1,7 +1,10 @@
1
1
  import type { OrganizationMemberRole } from "../../modules/organization/memberTypes";
2
2
  import { type AuthUser } from "./authContext";
3
+ import { membershipRepository as defaultMembershipRepository } from "./membershipContext";
4
+ type MembershipRepositoryLike = Pick<typeof defaultMembershipRepository, "listForUser" | "findMembership" | "listForOrganization" | "addMember" | "removeMember">;
3
5
  declare class MembershipService {
4
- constructor();
6
+ private readonly members;
7
+ constructor(members?: MembershipRepositoryLike);
5
8
  listOrganizationIdsForUser(userId: number): Promise<number[]>;
6
9
  getOrgRole(userId: number, organizationId: number): Promise<OrganizationMemberRole | null>;
7
10
  requireOrgAccess(organizationId: number, minimumRole?: OrganizationMemberRole, user?: AuthUser | null): Promise<OrganizationMemberRole>;
@@ -16,5 +19,6 @@ declare class MembershipService {
16
19
  removeMember(organizationId: number, userId: number): Promise<boolean>;
17
20
  }
18
21
  declare function resolveMembershipService(): MembershipService;
22
+ export type { MembershipRepositoryLike };
19
23
  export default MembershipService;
20
24
  export { resolveMembershipService };
@@ -9,6 +9,11 @@ type ExtendedQueryOptions<TEntity extends object> = QueryOptions<TEntity> & {
9
9
  };
10
10
  interface DatabaseConnection {
11
11
  unsafe<T>(query: string, params?: readonly unknown[]): Promise<T[]>;
12
+ begin?<T>(callback: (transaction: DatabaseConnection) => Promise<T>): Promise<T>;
13
+ close?(): Promise<void>;
14
+ }
15
+ interface SqlDatabaseConnection extends DatabaseConnection {
16
+ (strings: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
12
17
  }
13
18
  type ErrorFactory<TValue> = (value: TValue) => Error;
14
19
  declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TEntity & string> {
@@ -59,4 +64,4 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
59
64
  loadMorphToForChildren<TChild extends object, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>, repositoriesByType: ReadonlyMap<string, BaseRepository<TParent, OwnerKey>>, options?: Omit<QueryOptions<TParent>, "where">): Promise<Map<TChild[MorphIdKey], TParent>>;
60
65
  }
61
66
  export default BaseRepository;
62
- export type { DatabaseConnection };
67
+ export type { DatabaseConnection, SqlDatabaseConnection };
@@ -3,5 +3,6 @@ type ActiveDatabaseHandle = {
3
3
  };
4
4
  declare function runWithDatabaseConnection<T>(connection: ActiveDatabaseHandle, callback: () => T | Promise<T>): T | Promise<T>;
5
5
  declare function getActiveDatabaseConnection<T extends ActiveDatabaseHandle>(fallback: T): T;
6
+ declare function hasActiveDatabaseConnection(): boolean;
6
7
  export type { ActiveDatabaseHandle };
7
- export { getActiveDatabaseConnection, runWithDatabaseConnection };
8
+ export { getActiveDatabaseConnection, hasActiveDatabaseConnection, runWithDatabaseConnection };
@@ -0,0 +1,6 @@
1
+ import type { SqlDatabaseConnection } from "./baseRepository";
2
+ declare function registerDefaultDatabasePool(connection: SqlDatabaseConnection): void;
3
+ declare function getDefaultDatabasePool(): SqlDatabaseConnection;
4
+ declare function getDefaultDatabaseQuery(): SqlDatabaseConnection;
5
+ declare function resetDefaultDatabasePoolForTests(): void;
6
+ export { getDefaultDatabasePool, getDefaultDatabaseQuery, registerDefaultDatabasePool, resetDefaultDatabasePoolForTests, };
@@ -0,0 +1,3 @@
1
+ import type { SqlDatabaseConnection } from "./baseRepository";
2
+ declare function createDatabaseQueryProxy(pool: SqlDatabaseConnection): SqlDatabaseConnection;
3
+ export { createDatabaseQueryProxy };
@@ -1,4 +1,4 @@
1
- import type { DatabaseConnection } from "./baseRepository";
2
- declare function resolveRepositoryConnection(): DatabaseConnection;
3
- declare const repositoryConnection: DatabaseConnection;
1
+ import type { SqlDatabaseConnection } from "./baseRepository";
2
+ declare function resolveRepositoryConnection(): SqlDatabaseConnection;
3
+ declare const repositoryConnection: SqlDatabaseConnection;
4
4
  export { repositoryConnection, resolveRepositoryConnection };
@@ -5,7 +5,6 @@ interface DispatchWebhookPayload {
5
5
  payload: Record<string, unknown>;
6
6
  }
7
7
  declare class DispatchWebhookJob extends Job<DispatchWebhookPayload> {
8
- constructor();
9
8
  readonly maxAttempts = 3;
10
9
  readonly backoffMs = 2000;
11
10
  handle(payload: DispatchWebhookPayload): Promise<void>;
@@ -2,6 +2,8 @@ declare const DEFAULT_FETCH_TIMEOUT_MS = 10000;
2
2
  interface SafeFetchOptions {
3
3
  timeoutMs?: number;
4
4
  maxRedirects?: number;
5
+ allowHttp?: boolean;
6
+ resolveDns?: boolean;
5
7
  }
6
8
  declare function safeFetch(input: string, init?: RequestInit, options?: SafeFetchOptions): Promise<Response>;
7
9
  export type { SafeFetchOptions };
@@ -1,5 +1,20 @@
1
+ type DnsLookupResult = {
2
+ address: string;
3
+ family: number;
4
+ };
5
+ type DnsLookup = (hostname: string, options: {
6
+ all: true;
7
+ verbatim: true;
8
+ }) => Promise<DnsLookupResult[]>;
1
9
  declare function isBlockedHostname(hostname: string): boolean;
2
10
  declare function assertSafeOutboundUrl(rawUrl: string, options?: {
3
11
  allowHttp?: boolean;
4
12
  }): URL;
5
- export { assertSafeOutboundUrl, isBlockedHostname };
13
+ declare function isBlockedIpAddress(address: string): boolean;
14
+ declare function assertSafeOutboundUrlResolved(rawUrl: string, options?: {
15
+ allowHttp?: boolean;
16
+ resolveDns?: boolean;
17
+ }): Promise<URL>;
18
+ declare function setDnsLookupForTests(lookupFn: DnsLookup): void;
19
+ declare function resetDnsLookupForTests(): void;
20
+ export { assertSafeOutboundUrl, assertSafeOutboundUrlResolved, isBlockedHostname, isBlockedIpAddress, resetDnsLookupForTests, setDnsLookupForTests, };
@@ -12,8 +12,9 @@ interface S3StorageConfig {
12
12
  endpoint?: string;
13
13
  }
14
14
  declare class LocalStorageDriver implements StorageDriver {
15
- private readonly rootDirectory;
16
- constructor(rootDirectory: string);
15
+ private readonly rootDirectory?;
16
+ constructor(rootDirectory?: string | undefined);
17
+ private resolveRootDirectory;
17
18
  private resolvePath;
18
19
  put(path: string, contents: string | Uint8Array): Promise<string>;
19
20
  get(path: string): Promise<Uint8Array | null>;
@@ -37,5 +38,7 @@ declare function resolveS3Config(): S3StorageConfig;
37
38
  declare function createS3Client(config?: S3StorageConfig): S3Client;
38
39
  declare function createStorageDriver(): StorageDriver;
39
40
  declare function storage(): StorageManager;
41
+ /** Test hook: drop the process-wide storage singleton (e.g. after changing STORAGE_PATH). */
42
+ declare function resetDefaultStorage(): void;
40
43
  export type { S3StorageConfig, StorageDriver };
41
- export { createS3Client, createStorageDriver, LocalStorageDriver, resolveS3Config, S3StorageDriver, StorageManager, storage, };
44
+ export { createS3Client, createStorageDriver, LocalStorageDriver, resetDefaultStorage, resolveS3Config, S3StorageDriver, StorageManager, storage, };
@@ -4,4 +4,5 @@ type TransactionHandle = {
4
4
  };
5
5
  declare function applyTenantContextToTransaction(transaction: TransactionHandle, tenantId: number): Promise<void>;
6
6
  declare function runWithTenantDatabase<T>(tenant: TenantContext, callback: () => T | Promise<T>): Promise<T>;
7
- export { applyTenantContextToTransaction, runWithTenantDatabase };
7
+ declare function isInsideTenantDatabaseScope(tenantId?: number | undefined): boolean;
8
+ export { applyTenantContextToTransaction, isInsideTenantDatabaseScope, runWithTenantDatabase };
@@ -1,4 +1,4 @@
1
- import { type DatabaseConnection } from "./createConnection";
1
+ import type { DatabaseConnection } from "./createConnection";
2
2
  declare function getDatabase(): DatabaseConnection;
3
3
  declare function pingDatabase(connection?: DatabaseConnection): Promise<boolean>;
4
4
  declare function ensureDatabaseConnection(): Promise<DatabaseConnection>;
@@ -1,113 +1 @@
1
- // @bun
2
- // ../../src/core/errors/http.ts
3
- class HttpError extends Error {
4
- status;
5
- details;
6
- constructor(status, message, details) {
7
- super(message);
8
- this.name = new.target.name;
9
- this.status = status;
10
- this.details = details;
11
- }
12
- }
13
-
14
- class BadRequestError extends HttpError {
15
- constructor(message = "Bad Request", details) {
16
- super(400, message, details);
17
- }
18
- }
19
-
20
- class NotFoundError extends HttpError {
21
- constructor(message = "Not Found", details) {
22
- super(404, message, details);
23
- }
24
- }
25
-
26
- class ConflictError extends HttpError {
27
- constructor(message = "Conflict", details) {
28
- super(409, message, details);
29
- }
30
- }
31
-
32
- class UnprocessableEntityError extends HttpError {
33
- constructor(message = "Unprocessable Entity", details) {
34
- super(422, message, details);
35
- }
36
- }
37
-
38
- class ValidationError extends HttpError {
39
- constructor(message = "Validation failed", details) {
40
- super(422, message, details);
41
- }
42
- }
43
-
44
- class ForbiddenError extends HttpError {
45
- constructor(message = "Forbidden", details) {
46
- super(403, message, details);
47
- }
48
- }
49
-
50
- class UnauthorizedError extends HttpError {
51
- constructor(message = "Unauthorized", details) {
52
- super(401, message, details);
53
- }
54
- }
55
-
56
- class PayloadTooLargeError extends HttpError {
57
- constructor(message = "Payload Too Large", details) {
58
- super(413, message, details);
59
- }
60
- }
61
-
62
- class PreconditionFailedError extends HttpError {
63
- constructor(message = "Precondition Failed", details) {
64
- super(412, message, details);
65
- }
66
- }
67
-
68
- // ../../src/core/auth/authContext.ts
69
- import { AsyncLocalStorage } from "async_hooks";
70
- var authContext = new AsyncLocalStorage;
71
- function runWithAuthUser(user, callback) {
72
- return authContext.run(user, callback);
73
- }
74
- function currentAuthUser() {
75
- return authContext.getStore() ?? null;
76
- }
77
-
78
- // ../../src/core/auth/accessControl.ts
79
- var ROLE_RANK = {
80
- member: 1,
81
- admin: 2,
82
- owner: 3
83
- };
84
- function isGlobalAdmin(user) {
85
- return user?.role === "admin";
86
- }
87
- function hasMinimumOrgRole(role, minimum) {
88
- if (!role) {
89
- return false;
90
- }
91
- return ROLE_RANK[role] >= ROLE_RANK[minimum];
92
- }
93
- function requireAuthenticatedUser() {
94
- const user = currentAuthUser();
95
- if (!user) {
96
- throw new ForbiddenError("Authentication required.");
97
- }
98
- return user;
99
- }
100
- function resolveUserId(user) {
101
- const userId = typeof user.id === "number" ? user.id : Number(user.id);
102
- if (!Number.isInteger(userId) || userId <= 0) {
103
- throw new ForbiddenError("Invalid authenticated user.");
104
- }
105
- return userId;
106
- }
107
- export {
108
- resolveUserId,
109
- requireAuthenticatedUser,
110
- isGlobalAdmin,
111
- hasMinimumOrgRole,
112
- ROLE_RANK
113
- };
1
+ export * from "../../index.js";
@@ -1,15 +1 @@
1
- // @bun
2
- // ../../src/core/auth/authContext.ts
3
- import { AsyncLocalStorage } from "async_hooks";
4
- var authContext = new AsyncLocalStorage;
5
- function runWithAuthUser(user, callback) {
6
- return authContext.run(user, callback);
7
- }
8
- function currentAuthUser() {
9
- return authContext.getStore() ?? null;
10
- }
11
- export {
12
- runWithAuthUser,
13
- currentAuthUser,
14
- authContext
15
- };
1
+ export * from "../../index.js";