@assembly-js/app-foundation 0.0.46 → 0.0.47

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
@@ -14,6 +14,11 @@ A foundation update reads the sections between an app's version and the target:
14
14
  from every bullet except `[guidance]`, agent work from the `fixes.json` entries those `[action]`
15
15
  bullets name. Write `[fix]`, `[behavior]` and `[action]` bullets for the app's owner.
16
16
 
17
+ ## 0.0.47
18
+ - [new] Apps can read the text of uploaded PDFs with a built-in parser that keeps working after publishing.
19
+ - [action: server-pdf-text-extraction] PDF uploads that parsed in the preview but failed after publishing now parse correctly; apps that read PDFs with their own parser switch to the built-in one.
20
+ - [guidance] Tell the build agent why PDF and similar libraries break after deploy (they look up worker scripts and native bindings by their own path), to use the built-in PDF text helper, and to preview PDFs in a new tab or on a canvas rather than in an iframe.
21
+
17
22
  ## 0.0.46
18
23
  - [new] Pages, server actions, and routes can use an installer client that acts as the installing admin while a person is in the app.
19
24
  - [behavior] The action wrapper takes the actor from the verified bearer and ignores a userId query parameter.
package/CLAUDE.md CHANGED
@@ -622,7 +622,7 @@ Server components, route handlers, and server actions run in **Node on Vercel**
622
622
 
623
623
  - **Visual work** (draw a chart, render on a canvas) is **client** work — load the library with `next/dynamic` + `ssr: false`, never in server code.
624
624
  - **Fuzzy extraction** (pull fields from a document, classify, summarize) is usually better done through the **AI proxy** (see `assembly-ai`) than a parsing lib — no native binding, no serverless hazard.
625
- - **Genuine server-side data work** (transform an image, parse bytes) must use a Node-native library: add native/CommonJS packages (e.g. `sharp`) to `serverExternalPackages` in `app.config.ts` (bundling breaks the binding), and give any dep that's loaded dynamically or transitively a static, string-literal import in server code — the deploy tracer can't follow dynamic requires, so such a package silently won't exist in the deployed function even though preview works.
625
+ - **Genuine server-side data work** (transform an image, parse bytes) must use a Node-native library: add native/CommonJS packages (e.g. `sharp`) to `serverExternalPackages` in `app.config.ts` (bundling breaks the binding). A library that locates files at runtime — a worker script, a native binding, font data — by a path computed from its own location breaks after deploy: the bundler relocates its code and the deployed function only has the files reachable from literal imports, so the lookup fails even though preview has the full `node_modules`. Prefer a self-contained server build. **PDF text: `extractPdfText` from `@assembly-js/app-foundation/server`** — never `pdf-parse` or `pdfjs-dist` on the server; both look their worker and canvas up this way and fail on every deployed upload.
626
626
  - **Browser-only packages** (touch `window`/`document`/`DOMMatrix`/`HTMLCanvasElement` at import *or during operations*) can't run server-side at all — `serverExternalPackages` doesn't help and dynamic `import()` only defers the crash. Use a Node-native alternative or move the work client-side.
627
627
 
628
628
  ---
@@ -1464,6 +1464,7 @@ Use `AssemblyBridge.header.*` for breadcrumbs, CTAs, and actions menu. See `.cla
1464
1464
  - ❌ Don't add a manual close button (a `<DialogClose>` render or any X button) inside `DialogContent`. `DialogContent` already ships a built-in close button (`showCloseButton` defaults to `true`); adding another produces two overlapping X icons in the top-right corner. Only pass `showCloseButton={false}` when a custom affordance genuinely replaces the built-in one.
1465
1465
  - ❌ Don't edit `next.config.ts` — it's the platform-owned wrapper that keeps the preview working (cross-origin hydration, WebSocket-blocked networks), and `bun run build` fails if it's rewritten. Your Next configuration belongs in `app.config.ts`.
1466
1466
  - ❌ Don't import a native or browser-only library into server code without the serverless setup — `bun run build` passes but the deployed route crashes at request time. Decide the layer first per "Server runs in serverless Node" above.
1467
+ - ❌ Don't preview a PDF (or any file) in an `<iframe>`, `<embed>` or `<object>` — the portal frame is sandboxed and Chrome won't run its PDF viewer inside it, so the preview renders blank while the build passes. Open the pre-signed URL in a new tab (`assembly-storage` Pattern A) or render pages to a canvas client-side with `next/dynamic` + `ssr: false`.
1467
1468
  - ❌ Don't call a change verified, working, or safe to publish because `tsc --noEmit` and `bun run build` passed — name the checks that ran and the signed-in screens left unopened. See "What a passing build proves" above.
1468
1469
  - ❌ Don't pass a function — or an object that holds one, like a question/field config with `visibleWhen`, `validate`, `render`, or `format` callbacks — as a prop from a server component or page into a `'use client'` component. Props cross that boundary as serialized data, so the render throws `Functions cannot be passed directly to Client Components` and the page 500s for every visitor no matter what the record holds. `tsc --noEmit` and `bun run build` both pass. Keep such a config in a shared module the client component imports itself, or express the logic as data (`{ showWhen: { field: 'entityType', equals: 'llc' } }`) and interpret it client-side.
1469
1470
  - ❌ Don't use `Select` or `Combobox` to pick any Assembly person or organization record (clients, internal users, team members, staff, companies). Always use `UserCompanySelector` — in forms, filters, dropdowns, "assign to" controls, everywhere.
package/dist/pdf.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export interface PdfText {
2
+ /** Every page's text, pages separated by a newline. Empty when the PDF has no text layer (a scan). */
3
+ text: string;
4
+ pages: number;
5
+ }
6
+ export declare function extractPdfText(data: ArrayBuffer | Uint8Array): Promise<PdfText>;
package/dist/pdf.js ADDED
@@ -0,0 +1,15 @@
1
+ // unpdf ships a self-contained serverless build of pdf.js: the worker is inlined
2
+ // and it never loads @napi-rs/canvas. pdf-parse and pdfjs-dist locate both at
3
+ // runtime by paths computed from their own location, which the deploy tracer
4
+ // can't follow, so they pass in the sandbox and 500 once published. Loaded
5
+ // lazily so routes that never parse a PDF don't carry the bundle.
6
+ export async function extractPdfText(data) {
7
+ const { getDocumentProxy, extractText } = await import('unpdf');
8
+ // pdf.js rejects a Node Buffer outright, so hand it a plain Uint8Array view.
9
+ const bytes = data instanceof ArrayBuffer
10
+ ? new Uint8Array(data)
11
+ : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
12
+ const pdf = await getDocumentProxy(bytes);
13
+ const { text, totalPages } = await extractText(pdf, { mergePages: true });
14
+ return { text, pages: totalPages };
15
+ }
package/dist/server.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './notifications.js';
14
14
  export * from './cron.js';
15
15
  export * from './replace-rows.js';
16
16
  export * from './proxy.js';
17
+ export * from './pdf.js';
17
18
  export * from './api-errors.js';
18
19
  export * from './params.js';
19
20
  export * from './client.js';
package/dist/server.js CHANGED
@@ -18,6 +18,8 @@ export * from './notifications.js';
18
18
  export * from './cron.js';
19
19
  export * from './replace-rows.js';
20
20
  export * from './proxy.js';
21
+ // Lazy-loads its parser, so importing the barrel costs a route nothing until it parses a PDF.
22
+ export * from './pdf.js';
21
23
  // Returns NextResponse, so route handlers only — but nothing here reads a
22
24
  // secret, which is why a client file may still `import type` from them.
23
25
  export * from './api-errors.js';
package/fixes.json CHANGED
@@ -60,5 +60,19 @@
60
60
  "dependencies": ["@assembly-js/app-foundation"],
61
61
  "checks": [{ "of": "@assembly-js/app-foundation", "script": "check-proxy-csrf.mjs" }]
62
62
  }
63
+ },
64
+ {
65
+ "id": "server-pdf-text-extraction",
66
+ "version": "0.0.47",
67
+ "title": "PDF uploads that parsed in the preview but failed after publishing now parse correctly.",
68
+ "check": "Your app reads PDF text on the server with pdf-parse or pdfjs-dist. Those libraries locate their worker script and the optional canvas binding at runtime by paths computed from their own location; the preview has the full node_modules so it works there, but the deployed function only ships the files reachable from literal imports, so every PDF parse fails after publishing with \"DOMMatrix is not defined\" or \"Setting up fake worker failed\".\n\nReplace each server-side use with the foundation's helper: `import { extractPdfText } from '@assembly-js/app-foundation/server';` then `const { text, pages } = await extractPdfText(bytes);` where `bytes` is the Buffer, Uint8Array or ArrayBuffer the code already has (read back from S3 or from the request). `text` is the whole document with pages separated by newlines and is empty for a scanned PDF with no text layer, so keep whatever fallback the app had for that case. Keep what the app does with the text unchanged.\n\nThen remove what only existed to support the old parser: the pdf-parse, pdfjs-dist and @napi-rs/canvas dependencies, any GlobalWorkerOptions.workerSrc setup, any static `import '@napi-rs/canvas'` or worker import, and their entries in serverExternalPackages in app.config.ts. Do not touch client-side PDF rendering code.",
69
+ "detector": {
70
+ "forbidden": [
71
+ {
72
+ "id": "server-pdf-parser-import",
73
+ "pattern": "(from|import\\()\\s*['\"](pdf-parse|pdfjs-dist/legacy/build/pdf(\\.mjs)?)['\"]"
74
+ }
75
+ ]
76
+ }
63
77
  }
64
78
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [
@@ -65,5 +65,8 @@
65
65
  "helpers"
66
66
  ],
67
67
  "author": "Assembly",
68
- "license": "MIT"
68
+ "license": "MIT",
69
+ "dependencies": {
70
+ "unpdf": "^1.9.1"
71
+ }
69
72
  }
@@ -151,6 +151,7 @@ export async function POST(request: NextRequest) {
151
151
  if (!res.ok) throw new Error('S3 fetch failed: ' + res.status);
152
152
  const buffer = Buffer.from(await res.arrayBuffer());
153
153
  // ...process the buffer here; the file is already stored, no second upload.
154
+ // PDF text: const { text } = await extractPdfText(buffer) from '@assembly-js/app-foundation/server' (empty text = scanned PDF, no text layer).
154
155
  return NextResponse.json({ ok: true });
155
156
  } catch (err) {
156
157
  return handleRouteError(err, 'Processing failed');