@omg-dev/server 0.4.40 → 0.4.42

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/dist/index.mjs CHANGED
@@ -21,6 +21,9 @@ const ctx = {
21
21
  },
22
22
  get appId() {
23
23
  return ctxStore.getStore()?.appId;
24
+ },
25
+ get request() {
26
+ return ctxStore.getStore()?.request;
24
27
  }
25
28
  };
26
29
  //#endregion
@@ -2998,6 +3001,14 @@ const billing = {
2998
3001
  return agentGet(`/_billing/subscription?${new URLSearchParams({ externalRef }).toString()}`, "subscription");
2999
3002
  },
3000
3003
  /**
3004
+ * Verify a StoreKit 2 signed transaction and apply its entitlement.
3005
+ * The verified Apple appAccountToken is the only user binding. Do not send a
3006
+ * user id here. Apple requires appAccountToken to be a UUID.
3007
+ */
3008
+ async syncAppStoreTransaction(signedTransaction) {
3009
+ return agentPost("/_billing/app-store/transactions", { signedTransaction }, "syncAppStoreTransaction");
3010
+ },
3011
+ /**
3001
3012
  * Mint a hosted checkout for one of this app's end-users to buy `plan`, in
3002
3013
  * the APP OWNER's connected Polar org. Returns the checkout URL to redirect
3003
3014
  * to. The amount + product are resolved server-side from the active
@@ -3213,6 +3224,9 @@ async function createVibesServer(opts) {
3213
3224
  const url = new URL(req.url);
3214
3225
  let filePath = path.join(staticDir, url.pathname);
3215
3226
  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) return new Response(Bun.file(filePath));
3227
+ const dirIndexPath = path.join(filePath, "index.html");
3228
+ if (fs.existsSync(dirIndexPath) && fs.statSync(dirIndexPath).isFile()) return new Response(Bun.file(dirIndexPath));
3229
+ if (opts.staticFallthrough) return null;
3216
3230
  const indexPath = path.join(staticDir, "index.html");
3217
3231
  if (fs.existsSync(indexPath)) return new Response(Bun.file(indexPath));
3218
3232
  return null;
@@ -3229,7 +3243,8 @@ async function createVibesServer(opts) {
3229
3243
  userId: authResult?.userId ?? null,
3230
3244
  userEmail: authResult?.userEmail,
3231
3245
  userName: authResult?.userName,
3232
- appId: authResult?.appId
3246
+ appId: authResult?.appId,
3247
+ request: req
3233
3248
  }, () => handleRequest(req, routes));
3234
3249
  },
3235
3250
  async fetch(req) {
@@ -3252,6 +3267,7 @@ async function createVibesServer(opts) {
3252
3267
  if (url.pathname.startsWith("/api/")) return instance.apiHandler(req);
3253
3268
  const staticResponse = await serveStatic(req);
3254
3269
  if (staticResponse) return staticResponse;
3270
+ if (opts.notFound) return opts.notFound(req);
3255
3271
  return Response.json({ error: "Not found" }, { status: 404 });
3256
3272
  },
3257
3273
  migrate(newSchema) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omg-dev/server",
3
- "version": "0.4.40",
3
+ "version": "0.4.42",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -14,9 +14,9 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@restatedev/restate-sdk": "1.14.5",
17
- "@omg-dev/auth": "0.4.40",
18
- "@omg-dev/schema": "0.4.40",
19
- "@omg-dev/stream": "0.4.40",
17
+ "@omg-dev/auth": "0.4.42",
18
+ "@omg-dev/schema": "0.4.42",
19
+ "@omg-dev/stream": "0.4.42",
20
20
  "web-push": "^3.6.7"
21
21
  },
22
22
  "scripts": {
package/src/billing.ts CHANGED
@@ -128,6 +128,12 @@ export interface CheckoutOptions {
128
128
  amountMicros?: number
129
129
  }
130
130
 
131
+ export interface AppStoreTransactionResult {
132
+ plan: string | null
133
+ providerSubscriptionId: string
134
+ duplicate?: boolean
135
+ }
136
+
131
137
  export interface GrantOptions {
132
138
  idempotencyKey: string
133
139
  grantKey?: string
@@ -265,6 +271,19 @@ export const billing = {
265
271
  )
266
272
  },
267
273
 
274
+ /**
275
+ * Verify a StoreKit 2 signed transaction and apply its entitlement.
276
+ * The verified Apple appAccountToken is the only user binding. Do not send a
277
+ * user id here. Apple requires appAccountToken to be a UUID.
278
+ */
279
+ async syncAppStoreTransaction(signedTransaction: string): Promise<AppStoreTransactionResult> {
280
+ return agentPost<AppStoreTransactionResult>(
281
+ "/_billing/app-store/transactions",
282
+ { signedTransaction },
283
+ "syncAppStoreTransaction",
284
+ )
285
+ },
286
+
268
287
  /**
269
288
  * Mint a hosted checkout for one of this app's end-users to buy `plan`, in
270
289
  * the APP OWNER's connected Polar org. Returns the checkout URL to redirect
package/src/ctx.ts CHANGED
@@ -6,6 +6,8 @@ export interface VibesCtx {
6
6
  userEmail?: string
7
7
  userName?: string
8
8
  appId?: string
9
+ /** The inbound request for request-scoped server decisions such as GeoIP. */
10
+ request?: Request
9
11
  /**
10
12
  * True when the current ctx was created by the trigger dispatcher (cron
11
13
  * tick or event delivery), not from an inbound HTTP request. Lets handlers
@@ -30,4 +32,7 @@ export const ctx = {
30
32
  get appId() {
31
33
  return ctxStore.getStore()?.appId
32
34
  },
35
+ get request() {
36
+ return ctxStore.getStore()?.request
37
+ },
33
38
  }
package/src/index.ts CHANGED
@@ -203,6 +203,21 @@ export interface VibesServerOptions {
203
203
  */
204
204
  workflows?: WorkflowEntry[]
205
205
  staticDir?: string
206
+ /**
207
+ * When true, a static miss returns null instead of the index.html SPA shell,
208
+ * letting the caller fall through to its own handler. Set by SSR builds
209
+ * (TanStack Start), where the framework renders every HTML document and an
210
+ * SPA fallback would shadow real routes with a stale shell.
211
+ */
212
+ staticFallthrough?: boolean
213
+ /**
214
+ * Terminal handler for requests nothing else claimed. Without it `fetch`
215
+ * answers a JSON 404. SSR builds point this at the framework's own fetch
216
+ * handler so page requests get server-rendered — it is the seam that makes
217
+ * "vibes owns /api and assets, the framework owns documents" explicit
218
+ * rather than inferred from a 404 status.
219
+ */
220
+ notFound?: (req: Request) => Response | Promise<Response>
206
221
  schema?: Schema
207
222
  /**
208
223
  * When false, skip DDL migrations at startup — but still register user-scoped
@@ -569,6 +584,18 @@ export async function createVibesServer(opts: VibesServerOptions): Promise<Vibes
569
584
  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
570
585
  return new Response(Bun.file(filePath))
571
586
  }
587
+ // Directory index — "/pricing" → "dist/pricing/index.html". This is where
588
+ // the build-time prerender writes per-route static HTML (see prerender.ts),
589
+ // so it MUST be tried before the SPA fallback below: otherwise every route
590
+ // serves the homepage's prerendered markup and the route's own bake is dead
591
+ // weight. Kept ahead of the SSR fallthrough too: a framework that does emit
592
+ // per-route static HTML should have it served rather than re-rendered.
593
+ const dirIndexPath = path.join(filePath, "index.html")
594
+ if (fs.existsSync(dirIndexPath) && fs.statSync(dirIndexPath).isFile()) {
595
+ return new Response(Bun.file(dirIndexPath))
596
+ }
597
+ // SSR owns unmatched paths — see staticFallthrough.
598
+ if (opts.staticFallthrough) return null
572
599
  // SPA fallback
573
600
  const indexPath = path.join(staticDir, "index.html")
574
601
  if (fs.existsSync(indexPath)) {
@@ -593,6 +620,7 @@ export async function createVibesServer(opts: VibesServerOptions): Promise<Vibes
593
620
  userEmail: authResult?.userEmail,
594
621
  userName: authResult?.userName,
595
622
  appId: authResult?.appId,
623
+ request: req,
596
624
  },
597
625
  () => handleRequest(req, routes),
598
626
  )
@@ -647,6 +675,7 @@ export async function createVibesServer(opts: VibesServerOptions): Promise<Vibes
647
675
  const staticResponse = await serveStatic(req)
648
676
  if (staticResponse) return staticResponse
649
677
 
678
+ if (opts.notFound) return opts.notFound(req)
650
679
  return Response.json({ error: "Not found" }, { status: 404 })
651
680
  },
652
681