@getstrata/core 0.5.82 → 0.5.85

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,18 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.85
4
+
5
+ - `applicationRegistry` prefers the latest `Symbol.for("@getstrata/applicationContext")` on `globalThis` so a stale module-local context cannot hide the bootstrapped app after `build:framework`.
6
+
7
+ ## 0.5.84
8
+
9
+ - `eventBus` is a `Symbol.for("@getstrata/eventBus")` process singleton so model writes from a built `@getstrata/core/database/baseRepository` bundle reach app listeners that imported a different copy of `@getstrata/core/events` (GitHub Actions `build:framework` before tests).
10
+
11
+ ## 0.5.83
12
+
13
+ - `MEMBER_ABILITIES` includes `auth:tokens:delete` so Jetstream-style personal access token revoke works for members (`DELETE /auth/tokens/:id`). HTML `/account/tokens/:id/revoke` was already authenticated-only.
14
+ - `buildOtpauthUrl()` defaults the issuer through `appDisplayName()` (`APP_NAME`).
15
+
3
16
  ## 0.5.82
4
17
 
5
18
  - `MEMBER_ABILITIES` includes `organizations:create` so Jetstream-style extra teams work for members (HTML `POST /organizations` and JSON `POST /organizations`). `OrganizationPolicy.create` already allowed any authenticated user.
package/README.md CHANGED
@@ -82,7 +82,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
82
82
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
83
83
 
84
84
  1. Add `NPM_TOKEN` to GitHub repository secrets.
85
- 2. Tag a release: `git tag v0.5.84 && git push origin v0.5.84`
85
+ 2. Tag a release: `git tag v0.5.85 && git push origin v0.5.85`
86
86
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
87
87
 
88
88
  Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
@@ -1,5 +1,5 @@
1
- declare const MEMBER_ABILITIES: readonly ["organizations:read", "organizations:create", "projects:read", "projects:create", "tasks:read", "tasks:create", "comments:read", "comments:create", "attachments:read", "attachments:create", "auth:tokens:read", "auth:tokens:write"];
2
- declare const ADMIN_ABILITIES: readonly ["organizations:read", "organizations:create", "projects:read", "projects:create", "tasks:read", "tasks:create", "comments:read", "comments:create", "attachments:read", "attachments:create", "auth:tokens:read", "auth:tokens:write", "organizations:create", "organizations:update", "organizations:delete", "projects:update", "projects:delete", "tasks:update", "tasks:delete", "comments:update", "comments:delete", "attachments:delete", "webhooks:read", "webhooks:write", "audit:read"];
1
+ declare const MEMBER_ABILITIES: readonly ["organizations:read", "organizations:create", "projects:read", "projects:create", "tasks:read", "tasks:create", "comments:read", "comments:create", "attachments:read", "attachments:create", "auth:tokens:read", "auth:tokens:write", "auth:tokens:delete"];
2
+ declare const ADMIN_ABILITIES: readonly ["organizations:read", "organizations:create", "projects:read", "projects:create", "tasks:read", "tasks:create", "comments:read", "comments:create", "attachments:read", "attachments:create", "auth:tokens:read", "auth:tokens:write", "auth:tokens:delete", "organizations:create", "organizations:update", "organizations:delete", "projects:update", "projects:delete", "tasks:update", "tasks:delete", "comments:update", "comments:delete", "attachments:delete", "webhooks:read", "webhooks:write", "audit:read"];
3
3
  declare const PLATFORM_ADMIN_ABILITIES: readonly ["*"];
4
4
  interface AbilityCatalog {
5
5
  member: readonly string[];
@@ -5,6 +5,7 @@ declare class EventBus {
5
5
  listen(event: string, listener: EventListener): () => void;
6
6
  dispatch(event: string, payload: unknown): Promise<void>;
7
7
  }
8
+ declare function readSharedEventBus(): EventBus;
8
9
  declare const eventBus: EventBus;
9
10
  export type { EventListener };
10
- export { EventBus, eventBus };
11
+ export { EventBus, eventBus, readSharedEventBus };
@@ -1,4 +1,4 @@
1
1
  export type { EventListener } from "./eventBus";
2
- export { EventBus, eventBus } from "./eventBus";
2
+ export { EventBus, eventBus, readSharedEventBus } from "./eventBus";
3
3
  declare function modelEventName(tableName: string, action: string): string;
4
4
  export { modelEventName };
@@ -57,7 +57,8 @@ var MEMBER_ABILITIES = [
57
57
  "attachments:read",
58
58
  "attachments:create",
59
59
  "auth:tokens:read",
60
- "auth:tokens:write"
60
+ "auth:tokens:write",
61
+ "auth:tokens:delete"
61
62
  ];
62
63
  var ADMIN_ABILITIES = [
63
64
  ...MEMBER_ABILITIES,
@@ -14,8 +14,9 @@ class DispatchWebhookJob extends Job {
14
14
  maxAttempts = 3;
15
15
  backoffMs = 2000;
16
16
  async handle(payload) {
17
- const tenant = await resolveTenant(payload.tenantId) ?? {
18
- id: payload.tenantId,
17
+ const tenantId = Number(payload.tenantId);
18
+ const tenant = await resolveTenant(tenantId) ?? {
19
+ id: tenantId,
19
20
  slug: "job",
20
21
  plan: "free",
21
22
  region: "eu"
@@ -26,13 +27,16 @@ class DispatchWebhookJob extends Job {
26
27
  }
27
28
  }
28
29
  async deliver(payload) {
29
- const rows = await db`
30
- SELECT id, url, secret
31
- FROM webhook
32
- WHERE id = ${payload.webhookId} AND active = TRUE
33
- LIMIT 1
34
- `;
35
- const webhook = rows[0];
30
+ const queuedUrl = typeof payload.url === "string" ? payload.url : "";
31
+ const queuedSecret = typeof payload.secret === "string" ? payload.secret : "";
32
+ const queued = queuedUrl !== "" && queuedSecret !== "" ? { id: payload.webhookId, url: queuedUrl, secret: queuedSecret } : null;
33
+ const rows = queued ? [] : await db`
34
+ SELECT id, url, secret
35
+ FROM webhook
36
+ WHERE id = ${payload.webhookId} AND active = TRUE
37
+ LIMIT 1
38
+ `;
39
+ const webhook = queued ?? rows[0];
36
40
  if (!webhook) {
37
41
  return;
38
42
  }
@@ -66,10 +66,16 @@ var PUBLIC_ROUTE_DESCRIPTIONS = {
66
66
  "POST /auth/tokens": "Create API token",
67
67
  "DELETE /auth/tokens/:id": "Revoke API token",
68
68
  "GET /users/me/export": "GDPR export of user data",
69
+ "GET /users/me/current-organization": "Current Jetstream organization",
70
+ "PUT /users/me/current-organization": "Switch current Jetstream organization",
71
+ "GET /users/me/invitations": "List pending team invitations for the signed-in email",
72
+ "POST /users/me/invitations/:id/accept": "Accept a pending team invitation",
73
+ "DELETE /users/me/invitations/:id": "Decline a pending team invitation",
69
74
  "DELETE /users/me": "GDPR account erasure (anonymize user, revoke tokens)",
70
75
  "GET /organizations": "List organizations",
71
76
  "POST /organizations": "Create organization",
72
77
  "GET /organizations/:id/members": "List organization members",
78
+ "POST /organizations/:id/invitations/:invitationId/resend": "Resend a pending team invitation and rotate its token",
73
79
  "GET /projects": "List projects",
74
80
  "POST /projects": "Create project",
75
81
  "GET /tasks": "List tasks",
@@ -1,6 +1,62 @@
1
1
  // @bun
2
2
  // ../../src/core/security/totp.ts
3
3
  import { createHmac, randomBytes } from "crypto";
4
+
5
+ // ../../src/core/runtime/appKeyPrefix.ts
6
+ function appKeyPrefix() {
7
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
8
+ }
9
+ function appCookieName(kind) {
10
+ return `${appKeyPrefix()}_${kind}`;
11
+ }
12
+ function appDevSecret(kind) {
13
+ return `${appKeyPrefix()}-dev-${kind}`;
14
+ }
15
+ function namespacedRedisKey(kind) {
16
+ return `${appKeyPrefix()}:${kind}`;
17
+ }
18
+ function smtpEhloHost() {
19
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
20
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
21
+ return safe || "strata.local";
22
+ }
23
+ function siemEventType() {
24
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
25
+ }
26
+ function appUserAgent() {
27
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
28
+ }
29
+ function otelServiceName() {
30
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
+ }
32
+ function webhookSignatureHeader() {
33
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
+ }
35
+ function appDisplayName() {
36
+ return process.env.APP_NAME?.trim() || "WorkHub";
37
+ }
38
+ function appEnv() {
39
+ return process.env.APP_ENV?.trim() || "local";
40
+ }
41
+ function appUrl() {
42
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
43
+ }
44
+ function apiPrefix() {
45
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
46
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
47
+ const trimmed = withSlash.replace(/\/+$/, "");
48
+ return trimmed || "/api/v1";
49
+ }
50
+ function sdkClientClassName() {
51
+ const override = process.env.APP_SDK_CLASS?.trim();
52
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
53
+ return override;
54
+ }
55
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
56
+ return fromName ? `${fromName}Client` : "AppClient";
57
+ }
58
+
59
+ // ../../src/core/security/totp.ts
4
60
  function decodeBase32(input) {
5
61
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
6
62
  const normalized = input.replace(/=+$/u, "").toUpperCase();
@@ -35,7 +91,7 @@ function generateTotpSecret(byteLength = 20) {
35
91
  return encodeBase32(randomBytes(byteLength));
36
92
  }
37
93
  function buildOtpauthUrl(options) {
38
- const issuer = options.issuer?.trim() || process.env.APP_NAME?.trim() || "WorkHub";
94
+ const issuer = options.issuer?.trim() || appDisplayName();
39
95
  const label = `${issuer}:${options.account}`;
40
96
  const params = new URLSearchParams({
41
97
  secret: options.secret,
package/dist/index.js CHANGED
@@ -208,7 +208,8 @@ var MEMBER_ABILITIES = [
208
208
  "attachments:read",
209
209
  "attachments:create",
210
210
  "auth:tokens:read",
211
- "auth:tokens:write"
211
+ "auth:tokens:write",
212
+ "auth:tokens:delete"
212
213
  ];
213
214
  var ADMIN_ABILITIES = [
214
215
  ...MEMBER_ABILITIES,
@@ -587,12 +588,10 @@ var appLogger = new Logger("app");
587
588
  var APPLICATION_CONTEXT_KEY = Symbol.for("@getstrata/applicationContext");
588
589
  var activeContext;
589
590
  function readStoredApplicationContext() {
590
- if (activeContext) {
591
- return activeContext;
592
- }
593
591
  const globalContext = globalThis[APPLICATION_CONTEXT_KEY];
594
592
  if (globalContext) {
595
593
  activeContext = globalContext;
594
+ return activeContext;
596
595
  }
597
596
  return activeContext;
598
597
  }
@@ -1531,7 +1530,17 @@ class EventBus {
1531
1530
  }
1532
1531
  }
1533
1532
  }
1534
- var eventBus = new EventBus;
1533
+ var EVENT_BUS_KEY = Symbol.for("@getstrata/eventBus");
1534
+ function readSharedEventBus() {
1535
+ const globalBus = globalThis[EVENT_BUS_KEY];
1536
+ if (globalBus) {
1537
+ return globalBus;
1538
+ }
1539
+ const bus = new EventBus;
1540
+ globalThis[EVENT_BUS_KEY] = bus;
1541
+ return bus;
1542
+ }
1543
+ var eventBus = readSharedEventBus();
1535
1544
 
1536
1545
  // ../../src/core/events/index.ts
1537
1546
  function modelEventName(tableName, action) {
@@ -4,6 +4,8 @@ interface DispatchWebhookPayload {
4
4
  tenantId: number;
5
5
  event: string;
6
6
  payload: Record<string, unknown>;
7
+ url?: string;
8
+ secret?: string;
7
9
  }
8
10
  declare class DispatchWebhookJob extends Job<DispatchWebhookPayload> {
9
11
  readonly maxAttempts = 3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.82",
3
+ "version": "0.5.85",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",