@trylight/app 0.1.9 → 0.2.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.
Files changed (69) hide show
  1. package/README.md +3 -18
  2. package/dist/auth/config.d.ts +5 -0
  3. package/dist/auth/config.js +16 -0
  4. package/dist/auth/context.d.ts +11 -0
  5. package/dist/auth/context.js +1 -0
  6. package/dist/auth/core.d.ts +64 -0
  7. package/dist/auth/core.js +50 -0
  8. package/dist/auth/db.d.ts +7 -0
  9. package/dist/auth/db.js +57 -0
  10. package/dist/auth/lookup.d.ts +47 -0
  11. package/dist/auth/lookup.js +120 -0
  12. package/dist/auth/mode.d.ts +8 -0
  13. package/dist/auth/mode.js +52 -0
  14. package/dist/auth/next-path.d.ts +1 -0
  15. package/dist/auth/next-path.js +10 -0
  16. package/dist/auth/plaza.d.ts +68 -0
  17. package/dist/auth/plaza.js +154 -0
  18. package/dist/auth/problems.d.ts +4 -0
  19. package/dist/auth/problems.js +36 -0
  20. package/dist/auth/users.d.ts +8 -0
  21. package/dist/auth/users.js +25 -0
  22. package/dist/brand.generated.d.ts +2 -0
  23. package/dist/brand.generated.js +3 -0
  24. package/dist/chat/paths.d.ts +10 -0
  25. package/dist/chat/paths.js +10 -0
  26. package/dist/chat/server.d.ts +24 -0
  27. package/dist/chat/server.js +161 -0
  28. package/dist/client.d.ts +3 -3
  29. package/dist/client.js +2 -2
  30. package/dist/db/cli.js +3 -40
  31. package/dist/db/migrations.d.ts +2 -2
  32. package/dist/db/migrations.js +2 -2
  33. package/dist/invoke/contracts.d.ts +28 -0
  34. package/dist/invoke/contracts.js +1 -0
  35. package/dist/invoke/error.d.ts +11 -0
  36. package/dist/invoke/error.js +13 -0
  37. package/dist/invoke/index.d.ts +73 -0
  38. package/dist/invoke/index.js +274 -0
  39. package/dist/invoke/paths.d.ts +2 -0
  40. package/dist/invoke/paths.js +2 -0
  41. package/dist/invoke/server.d.ts +45 -0
  42. package/dist/invoke/server.js +259 -0
  43. package/dist/platform/index.d.ts +71 -0
  44. package/dist/platform/index.js +80 -0
  45. package/dist/platform/pane-bridge.d.ts +1 -0
  46. package/dist/platform/pane-bridge.js +216 -0
  47. package/dist/platform/server-fn-door.d.ts +27 -0
  48. package/dist/platform/server-fn-door.js +32 -0
  49. package/dist/platform.d.ts +2 -2
  50. package/dist/platform.js +3 -3
  51. package/dist/request-error.d.ts +10 -0
  52. package/dist/request-error.js +29 -0
  53. package/dist/schema.d.ts +1 -1
  54. package/dist/server/db-retry.d.ts +7 -0
  55. package/dist/server/db-retry.js +58 -0
  56. package/dist/server/entities.js +1 -1
  57. package/dist/server/health.d.ts +17 -0
  58. package/dist/server/health.js +42 -0
  59. package/dist/server/index.d.ts +7 -6
  60. package/dist/server/index.js +7 -6
  61. package/dist/server/sentry-tunnel.d.ts +6 -0
  62. package/dist/server/sentry-tunnel.js +52 -0
  63. package/dist/server/session.d.ts +1 -1
  64. package/dist/server/session.js +2 -2
  65. package/dist/uuid.d.ts +1 -0
  66. package/dist/uuid.js +7 -0
  67. package/package.json +12 -10
  68. package/dist/db/baseline.d.ts +0 -36
  69. package/dist/db/baseline.js +0 -439
@@ -1,14 +1,15 @@
1
1
  import { type PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
- import { type Contracts } from "@shubh90/app-runtime/invoke/server";
2
+ import { type Contracts } from "../invoke/server.js";
3
3
  export type { SessionState, SessionUser, SignIn } from "./session.js";
4
- export { cookieAttributes, safeNext, SESSION_COOKIE } from "@shubh90/app-runtime/auth/core";
5
- export { defineContracts, InvocationError, type Contracts } from "@shubh90/app-runtime/invoke/server";
6
- export { createHealthHandler, createSentryTunnelHandler } from "@shubh90/app-runtime/server";
4
+ export { cookieAttributes, safeNext, SESSION_COOKIE } from "../auth/core.js";
5
+ export { defineContracts, InvocationError, type Contracts } from "../invoke/server.js";
6
+ export { createHealthHandler } from "./health.js";
7
+ export { createSentryTunnelHandler } from "./sentry-tunnel.js";
7
8
  export declare function createLightServer<S extends Record<string, unknown>, C extends Contracts>(options: {
8
9
  schema: S;
9
10
  contracts: C;
10
11
  }): {
11
- auth: import("@shubh90/app-runtime/auth/core").MiiAuthCore;
12
+ auth: import("../auth/core.js").MiiAuthCore;
12
13
  session: (request: Request) => Promise<import("./session.js").SessionState>;
13
14
  /** Whether sign-in is configured (a bare local dev server has none). */
14
15
  configured: () => boolean;
@@ -21,7 +22,7 @@ export declare function createLightServer<S extends Record<string, unknown>, C e
21
22
  /** `/api/light/image`: a row's photo or logo, from the app's own copy (kept on the first view). */
22
23
  image: (request: Request) => Promise<Response>;
23
24
  /** Server-side starts and reads of the app's contracts. */
24
- invoke: import("@shubh90/app-runtime/invoke/server").MiiInvoke<C>;
25
+ invoke: import("../invoke/server.js").MiiInvoke<C>;
25
26
  /** `/api/mii/invoke/*`: the contract buttons, forwarded to the platform as the person. */
26
27
  invokeRoute: (request: Request) => Promise<Response>;
27
28
  /** `/api/mii/chat/*`: the chat bubble's calls to the org's agents. */
@@ -3,16 +3,17 @@
3
3
  // images, contracts, chat). Server-only.
4
4
  import { drizzle } from "drizzle-orm/postgres-js";
5
5
  import postgres from "postgres";
6
- import { createMiiAuthCore } from "@shubh90/app-runtime/auth/core";
7
- import { createMiiChatHandler } from "@shubh90/app-runtime/chat/server";
8
- import { createMiiInvoke } from "@shubh90/app-runtime/invoke/server";
6
+ import { createMiiAuthCore } from "../auth/core.js";
7
+ import { createMiiChatHandler } from "../chat/server.js";
8
+ import { createMiiInvoke } from "../invoke/server.js";
9
9
  import { createEntitiesHandler } from "./entities.js";
10
10
  import { env } from "./env.js";
11
11
  import { createImageHandler } from "./images.js";
12
12
  import { readSession, signIn } from "./session.js";
13
- export { cookieAttributes, safeNext, SESSION_COOKIE } from "@shubh90/app-runtime/auth/core";
14
- export { defineContracts, InvocationError } from "@shubh90/app-runtime/invoke/server";
15
- export { createHealthHandler, createSentryTunnelHandler } from "@shubh90/app-runtime/server";
13
+ export { cookieAttributes, safeNext, SESSION_COOKIE } from "../auth/core.js";
14
+ export { defineContracts, InvocationError } from "../invoke/server.js";
15
+ export { createHealthHandler } from "./health.js";
16
+ export { createSentryTunnelHandler } from "./sentry-tunnel.js";
16
17
  /** Connections one server process holds open to the app's database. */
17
18
  const POOL_MAX = 5;
18
19
  export function createLightServer(options) {
@@ -0,0 +1,6 @@
1
+ export type SentryTunnelOptions = {
2
+ /** Test hook; defaults to global fetch. */
3
+ readonly fetch?: typeof fetch;
4
+ };
5
+ /** POST for /monitoring: forward the envelope, answer what Sentry answered. */
6
+ export declare function createSentryTunnelHandler({ fetch: doFetch }?: SentryTunnelOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1,52 @@
1
+ // The Sentry tunnel: browsers post error envelopes to this app's own
2
+ // /monitoring route, which forwards them to Sentry — because ad-blockers
3
+ // block *.sentry.io from the browser, and a platform whose error reporting
4
+ // dies to a browser extension is blind exactly where it matters.
5
+ //
6
+ // The envelope's own header names the DSN, so the route needs no
7
+ // configuration; it forwards only to hosts that are plainly Sentry's, so an
8
+ // app cannot be turned into an open relay by a crafted envelope.
9
+ import { z } from "zod/v4";
10
+ const ALLOWED_HOST = /(^|\.)(sentry\.io|ingest\.sentry\.io|ingest\.[a-z0-9-]+\.sentry\.io)$/;
11
+ /** The envelope's header line: the DSN it is for, whatever else it says. */
12
+ const envelopeHeader = z.object({ dsn: z.string() });
13
+ /** POST for /monitoring: forward the envelope, answer what Sentry answered. */
14
+ export function createSentryTunnelHandler({ fetch: doFetch = fetch } = {}) {
15
+ return async function tunnel(request) {
16
+ const envelope = await request.text();
17
+ const newline = envelope.indexOf("\n");
18
+ const headerLine = newline === -1 ? envelope : envelope.slice(0, newline);
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(headerLine);
22
+ }
23
+ catch {
24
+ return Response.json({ error: "Not a Sentry envelope." }, { status: 400 });
25
+ }
26
+ const header = envelopeHeader.safeParse(parsed);
27
+ if (!header.success) {
28
+ return Response.json({ error: "Envelope names no DSN." }, { status: 400 });
29
+ }
30
+ let dsn;
31
+ try {
32
+ dsn = new URL(header.data.dsn);
33
+ }
34
+ catch {
35
+ return Response.json({ error: "Not a Sentry envelope." }, { status: 400 });
36
+ }
37
+ if (!ALLOWED_HOST.test(dsn.hostname)) {
38
+ // A DSN pointing anywhere else would make this route an open relay.
39
+ return Response.json({ error: "DSN host is not Sentry." }, { status: 400 });
40
+ }
41
+ const projectId = dsn.pathname.replace(/^\/+/, "");
42
+ if (!/^\d+$/.test(projectId)) {
43
+ return Response.json({ error: "DSN names no project." }, { status: 400 });
44
+ }
45
+ const upstream = await doFetch(`https://${dsn.hostname}/api/${projectId}/envelope/`, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/x-sentry-envelope" },
48
+ body: envelope
49
+ });
50
+ return new Response(null, { status: upstream.status });
51
+ };
52
+ }
@@ -1,4 +1,4 @@
1
- import { type MiiAuthCore, type MiiUser } from "@shubh90/app-runtime/auth/core";
1
+ import { type MiiAuthCore, type MiiUser } from "../auth/core.js";
2
2
  export type SessionUser = Pick<MiiUser, "id" | "name" | "email" | "avatarUrl" | "role">;
3
3
  export type SessionState = {
4
4
  user: SessionUser | null;
@@ -1,6 +1,6 @@
1
1
  // Who is asking, as the app's door sees it, and signing in through the platform.
2
- import { signInWithPlaza } from "@shubh90/app-runtime/auth/core";
3
- import { readCookie } from "@shubh90/app-runtime/chat/server";
2
+ import { signInWithPlaza } from "../auth/core.js";
3
+ import { readCookie } from "../chat/server.js";
4
4
  import { env } from "./env.js";
5
5
  /** The session a request's cookie names, and whether the app is members-only. */
6
6
  export async function readSession(auth, request) {
package/dist/uuid.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function isUuid(value: string): boolean;
package/dist/uuid.js ADDED
@@ -0,0 +1,7 @@
1
+ import { z } from "zod/v4";
2
+ const uuidSchema = z.uuid();
3
+ // The platform's invocation ids are uuid columns: an id of any
4
+ // other shape names nothing, so it is refused here rather than forwarded.
5
+ export function isUuid(value) {
6
+ return uuidSchema.safeParse(value).success;
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trylight/app",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "The Light SDK every v2 org app depends on: entities (Drizzle tables with a typed client), sign-in, contracts and in-app chat.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -12,6 +12,7 @@
12
12
  "url": "https://github.com/shubh9/mii.git",
13
13
  "directory": "packages/app-sdk"
14
14
  },
15
+ "packageManager": "pnpm@11.0.9",
15
16
  "files": [
16
17
  "dist",
17
18
  "bin"
@@ -41,6 +42,15 @@
41
42
  "default": "./dist/vite.js"
42
43
  }
43
44
  },
45
+ "scripts": {
46
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
47
+ "typecheck": "tsc --noEmit",
48
+ "lint": "eslint src test --max-warnings 0",
49
+ "test": "node --test --import tsx \"test/**/*.test.ts\"",
50
+ "check": "tsx scripts/write-brand.ts --check && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
51
+ "prepack": "pnpm run build",
52
+ "brand": "tsx scripts/write-brand.ts"
53
+ },
44
54
  "peerDependencies": {
45
55
  "@tanstack/react-query": ">=5.69",
46
56
  "drizzle-orm": ">=0.45",
@@ -51,7 +61,6 @@
51
61
  "zod": ">=4"
52
62
  },
53
63
  "dependencies": {
54
- "@shubh90/app-runtime": "^0.10.3",
55
64
  "drizzle-kit": "0.31.11",
56
65
  "drizzle-zod": "^0.8.3",
57
66
  "ipaddr.js": "^2.5.0",
@@ -73,12 +82,5 @@
73
82
  "typescript-eslint": "^8.70.1",
74
83
  "vite": "^8.2.0",
75
84
  "zod": "^4.6.5"
76
- },
77
- "scripts": {
78
- "build": "rm -rf dist && tsc -p tsconfig.build.json",
79
- "typecheck": "tsc --noEmit",
80
- "lint": "eslint src test --max-warnings 0",
81
- "test": "node --test --import tsx \"test/**/*.test.ts\"",
82
- "check": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build"
83
85
  }
84
- }
86
+ }
@@ -1,36 +0,0 @@
1
- import { type Snapshot } from "./migrations.js";
2
- export type Baseline = {
3
- /** Each app table written to db/schema as an entity(), with the entity() columns it lacks (the first sync adds them). */
4
- tables: {
5
- name: string;
6
- lacks: string[];
7
- }[];
8
- /** App tables entity() can't key or say exactly, and why: plain tables in db/schema. */
9
- plain: {
10
- name: string;
11
- why: string;
12
- }[];
13
- /** Platform tables left as they are. */
14
- skipped: string[];
15
- /** Other things drizzle-kit found (views, sequences…) that db/schema doesn't hold. */
16
- leftOut: string[];
17
- /** What the first `light-db sync` will apply. */
18
- pending: string[];
19
- };
20
- /** Why the database can't become db/schema as it is; nothing was written. */
21
- export declare class BaselineRefused extends Error {
22
- }
23
- export declare function baseline(root: string, databaseUrl: string, schemaModule?: string): Promise<Baseline>;
24
- /** Runs drizzle-kit pull into a temporary folder: the tables as TypeScript, and drizzle's snapshot of them. */
25
- export declare function pull(databaseUrl: string): {
26
- source: string;
27
- snapshot: Snapshot;
28
- };
29
- /**
30
- * What drizzle-kit pull writes wrong: an empty-string default as `.default(')`,
31
- * and a column of a type it can't parse (tsvector, bytea) as `unknown(...)`
32
- * under a TODO naming the type, which becomes a custom type of that name.
33
- */
34
- export declare function repairPull(text: string): string;
35
- /** What a sync would run to make the `live` tables (as pulled) into `recorded`'s; none when they match. */
36
- export declare function driftFrom(live: Snapshot, recorded: Snapshot): Promise<string[]>;