@pylonsync/functions 0.11.6 → 0.13.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.
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-compat.d.ts +81 -0
- package/dist/index.d.ts +1 -1
- package/dist/production-build.d.ts +47 -0
- package/dist/server-bundle.d.ts +31 -0
- package/dist/ssr-client-bundler.d.ts +31 -3
- package/dist/ssr-fonts.d.ts +1 -1
- package/dist/ssr-runtime.d.ts +13 -1
- package/dist/stdout-fence.d.ts +18 -0
- package/dist/types.d.ts +75 -0
- package/package.json +8 -4
- package/src/build-cli.ts +36 -0
- package/src/build-compat.test.ts +173 -0
- package/src/build-compat.ts +430 -0
- package/src/index.ts +3 -0
- package/src/production-build.test.ts +269 -0
- package/src/production-build.ts +590 -0
- package/src/runtime-shard-ticket.test.ts +137 -0
- package/src/runtime.ts +83 -82
- package/src/server-bundle.test.ts +89 -0
- package/src/server-bundle.ts +63 -0
- package/src/ssr-client-bundler.ts +125 -13
- package/src/ssr-fonts.ts +1 -1
- package/src/ssr-og-runtime.ts +17 -5
- package/src/ssr-runtime.ts +112 -47
- package/src/stdout-fence.ts +69 -0
- package/src/types.ts +81 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** The manifest's `build` block, as the SDK writes it. */
|
|
2
|
+
export interface ManifestBuildConfig {
|
|
3
|
+
target?: string | string[];
|
|
4
|
+
polyfill?: false | "usage" | "entry";
|
|
5
|
+
css?: {
|
|
6
|
+
target?: string | string[];
|
|
7
|
+
};
|
|
8
|
+
sourcemap?: boolean;
|
|
9
|
+
server?: {
|
|
10
|
+
bundle?: boolean;
|
|
11
|
+
external?: string[];
|
|
12
|
+
};
|
|
13
|
+
include?: string[];
|
|
14
|
+
}
|
|
15
|
+
/** Resolved client compatibility settings. `null` means Bun's output ships
|
|
16
|
+
* unchanged (no target set). */
|
|
17
|
+
export interface ClientCompat {
|
|
18
|
+
/** Browserslist queries for JS. Empty when only `css.target` is set. */
|
|
19
|
+
jsTargets: string[];
|
|
20
|
+
/** Browserslist queries for CSS. */
|
|
21
|
+
cssTargets: string[];
|
|
22
|
+
polyfill: false | "usage" | "entry";
|
|
23
|
+
sourcemap: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Browser versions for each ECMAScript edition target. `es2019` becomes the
|
|
27
|
+
* oldest browsers with full ES2019 syntax support, so SWC and Lightning CSS
|
|
28
|
+
* (which work from browser versions) can use it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ES_TARGET_BROWSERS: Record<string, string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* The oldest version of each browser that can run the Pylon client runtime.
|
|
33
|
+
* The runtime ships as ES modules and loads route entries with dynamic
|
|
34
|
+
* `import()`. Syntax lowering cannot make an older browser load a page.
|
|
35
|
+
*/
|
|
36
|
+
export declare const RUNTIME_BROWSER_FLOOR: Record<string, number>;
|
|
37
|
+
/** Normalize a `target` value to browserslist queries. Throws on a value the
|
|
38
|
+
* build cannot honor. */
|
|
39
|
+
export declare function targetQueries(target: string | string[], field: string): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the client compatibility settings from the manifest's `build`
|
|
42
|
+
* block. Returns null when no JS or CSS target is set. `polyfill` without
|
|
43
|
+
* `target` is an error: it needs the browsers to polyfill for.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveClientCompat(build: ManifestBuildConfig | undefined): ClientCompat | null;
|
|
46
|
+
/** The browsers (from a browserslist result) that cannot run the client
|
|
47
|
+
* runtime. Browsers with no known floor are included. */
|
|
48
|
+
export declare function browsersBelowFloor(browsers: string[]): string[];
|
|
49
|
+
/** Remove core-js feature imports from `code` and add them to `into`. */
|
|
50
|
+
export declare function extractCoreJsImports(code: string, into: Set<string>): string;
|
|
51
|
+
/** Give a hashed file name (`name-<hash>.js`) a new hash derived from its old
|
|
52
|
+
* one and `salt`. Names without a hash are returned unchanged. */
|
|
53
|
+
export declare function rehashName(base: string, salt: string, hasher: (s: string) => string): string;
|
|
54
|
+
export interface CompatResult {
|
|
55
|
+
/** Old outdir-relative path → new outdir-relative path, for every renamed
|
|
56
|
+
* output. */
|
|
57
|
+
renamed: Map<string, string>;
|
|
58
|
+
/** Polyfill bundle, outdir-relative. Absent when polyfill is off or the
|
|
59
|
+
* targets need none. */
|
|
60
|
+
polyfills?: string;
|
|
61
|
+
/** Browsers the targets include that cannot run the client runtime. */
|
|
62
|
+
unsupported: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Lower every .js output under `outdir` to the JS targets, collect and bundle
|
|
66
|
+
* polyfills, and rename the outputs. `jsFiles` are outdir-relative,
|
|
67
|
+
* "/"-separated paths of Bun's .js outputs.
|
|
68
|
+
*/
|
|
69
|
+
export declare function applyClientCompat(opts: {
|
|
70
|
+
fs: any;
|
|
71
|
+
path: any;
|
|
72
|
+
cwd: string;
|
|
73
|
+
outdir: string;
|
|
74
|
+
jsFiles: string[];
|
|
75
|
+
compat: ClientCompat;
|
|
76
|
+
}): Promise<CompatResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Apply Lightning CSS to compiled CSS for the CSS targets: vendor prefixes,
|
|
79
|
+
* nesting and color-syntax lowering, minification.
|
|
80
|
+
*/
|
|
81
|
+
export declare function transformCss(cwd: string, css: string, filename: string, cssTargets: string[]): Promise<string>;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export type { WorkflowDefinition, WorkflowRun, WorkflowRunRequest, WorkflowRunne
|
|
|
26
26
|
export { resetDb, installTestIsolation } from "./testing";
|
|
27
27
|
export { slugifyName, availableSlug } from "./slugify";
|
|
28
28
|
export type { SsrResponse, SsrCookieOptions, SsrMetadata, Sitemap, SitemapEntry, Robots, RobotsRule, } from "./ssr-runtime";
|
|
29
|
-
export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, MemberRow, Workflows, VectorSearchQuery, VectorSearchResult, SearchResult, PaginationResult, Llm, LlmMessage, LlmContentBlock, LlmTool, LlmCompleteRequest, LlmCompleteResponse, LlmStreamEvent, Rooms, Domains, TenantDomainResult, TenantDomainDns, DomainAvailability, DomainContact, RegisterDomainOptions, RegisteredDomainResult, } from "./types";
|
|
29
|
+
export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, Shards, ShardsReader, ShardInfo, MemberRow, Workflows, VectorSearchQuery, VectorSearchResult, SearchResult, PaginationResult, Llm, LlmMessage, LlmContentBlock, LlmTool, LlmCompleteRequest, LlmCompleteResponse, LlmStreamEvent, Rooms, Domains, TenantDomainResult, TenantDomainDns, DomainAvailability, DomainContact, RegisterDomainOptions, RegisteredDomainResult, } from "./types";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** File name of the build info file. Its presence marks an artifact dir. */
|
|
2
|
+
export declare const BUILD_INFO_FILE = "pylon-build.json";
|
|
3
|
+
export interface ProductionBuildOptions {
|
|
4
|
+
/** Project root. */
|
|
5
|
+
cwd: string;
|
|
6
|
+
/** Absolute output directory. */
|
|
7
|
+
outDir: string;
|
|
8
|
+
/** App (routes) directory, relative to cwd. Default `app`. */
|
|
9
|
+
appDir?: string;
|
|
10
|
+
/** Functions directory, relative to cwd. Default `functions`. */
|
|
11
|
+
functionsDir?: string;
|
|
12
|
+
/** The app manifest. Default `<cwd>/pylon.manifest.json`. */
|
|
13
|
+
manifestPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ProductionBuildResult {
|
|
16
|
+
outDir: string;
|
|
17
|
+
functions: number;
|
|
18
|
+
workflows: number;
|
|
19
|
+
appModules: number;
|
|
20
|
+
clientRoutes: number;
|
|
21
|
+
externals: string[];
|
|
22
|
+
polyfills: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildProduction(opts: ProductionBuildOptions): Promise<ProductionBuildResult>;
|
|
25
|
+
/** The artifact-relative path for one `build.include` entry. Throws when the
|
|
26
|
+
* entry cannot be copied into the artifact as-is. */
|
|
27
|
+
export declare function checkInclude(path: any, cwd: string, outDir: string, inc: string, src: string, label?: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Every app module the SSR runtime can import: files named by convention
|
|
30
|
+
* anywhere under the app dir, plus every component and layout the manifest
|
|
31
|
+
* routes name. Keys are the runtime's module paths (`app/blog/page`).
|
|
32
|
+
*/
|
|
33
|
+
export declare function collectAppModules(fs: any, path: any, cwd: string, appDir: string, manifest: any): Array<{
|
|
34
|
+
key: string;
|
|
35
|
+
file: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Copy `roots` and their dependency closure (dependencies, optional
|
|
39
|
+
* dependencies that are installed, and peers) into `dest` (a node_modules
|
|
40
|
+
* dir).
|
|
41
|
+
* Each package resolves its own dependencies from its install location, so
|
|
42
|
+
* npm, pnpm, and Bun layouts all work. A package goes to the top of `dest`
|
|
43
|
+
* unless another version already holds that name; then it nests under the
|
|
44
|
+
* dependent package's own `node_modules`, where run-time resolution finds it
|
|
45
|
+
* first. Returns the copied package names.
|
|
46
|
+
*/
|
|
47
|
+
export declare function copyPackageClosure(fs: any, path: any, cwd: string, roots: string[], dest: string): string[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Loads one bundled module and returns its namespace. */
|
|
2
|
+
export type ModuleLoader = () => Promise<any>;
|
|
3
|
+
export interface PylonServerBundle {
|
|
4
|
+
/** Function name (file name without extension) → module loader. */
|
|
5
|
+
functions: Record<string, ModuleLoader>;
|
|
6
|
+
/** Workflow file name (without extension) → module loader. */
|
|
7
|
+
workflows: Record<string, ModuleLoader>;
|
|
8
|
+
/** Project-relative, extension-less, "/"-separated module path
|
|
9
|
+
* (`app/blog/page`) → module loader. Holds every module under the app
|
|
10
|
+
* dir that the SSR runtime can import: pages, layouts, boundaries,
|
|
11
|
+
* loading states, route handlers, OG image modules, metadata routes. */
|
|
12
|
+
modules: Record<string, ModuleLoader>;
|
|
13
|
+
/** The app's React and React DOM server, as bundled with the pages, so SSR
|
|
14
|
+
* renders with the same React instance the page modules import. */
|
|
15
|
+
react: any;
|
|
16
|
+
reactDomServer: any;
|
|
17
|
+
/** Client bundle directory, relative to the artifact root. */
|
|
18
|
+
clientDir: string;
|
|
19
|
+
/** Absolute paths of the files the OG image renderer reads at run time.
|
|
20
|
+
* In source mode it finds them next to its own source file and in
|
|
21
|
+
* `node_modules`; the bundle copies them next to the server output. */
|
|
22
|
+
ogAssets: {
|
|
23
|
+
resvgWasm: string;
|
|
24
|
+
interRegular: string;
|
|
25
|
+
interSemiBold: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare function serverBundle(): PylonServerBundle | null;
|
|
29
|
+
export declare function setServerBundle(bundle: PylonServerBundle): void;
|
|
30
|
+
/** Normalize a module path to the registry key form. */
|
|
31
|
+
export declare function moduleKey(relPath: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ManifestFonts } from "./ssr-fonts";
|
|
2
|
+
import { type ManifestBuildConfig } from "./build-compat";
|
|
2
3
|
type Send = (msg: Record<string, unknown>) => void;
|
|
3
4
|
interface BundleClientMessage {
|
|
4
5
|
type: "bundle_client";
|
|
@@ -70,8 +71,12 @@ export declare function generateLoadingRegistry(components: string[]): string;
|
|
|
70
71
|
export interface PylonBundleManifest {
|
|
71
72
|
/** Build identity — bumps every successful build. */
|
|
72
73
|
build_id: string;
|
|
73
|
-
/** Output root, relative to cwd
|
|
74
|
+
/** Output root, relative to cwd: `.pylon/client-build`, or `client` in a
|
|
75
|
+
* `pylon build` artifact. */
|
|
74
76
|
outdir: string;
|
|
77
|
+
/** core-js polyfill bundle (relative to outdir), loaded before any route
|
|
78
|
+
* entry. Present when `build.polyfill` is set and the targets need one. */
|
|
79
|
+
polyfills?: string;
|
|
75
80
|
/** Public URL prefix the Rust host serves chunks under. */
|
|
76
81
|
public_prefix: string;
|
|
77
82
|
/** routeComponentPath → file + imports for that route. */
|
|
@@ -108,6 +113,29 @@ export interface BuildOutput {
|
|
|
108
113
|
* from `getManifest` (in-process SSR path).
|
|
109
114
|
*/
|
|
110
115
|
export declare function buildClientBundle(appDirRel?: string): Promise<BuildOutput>;
|
|
116
|
+
/** Default client bundle directory, relative to the project root. */
|
|
117
|
+
export declare const CLIENT_BUILD_DIR = ".pylon/client-build";
|
|
118
|
+
/** The client bundle directory, relative to cwd. A production server bundle
|
|
119
|
+
* names its own; source mode uses `CLIENT_BUILD_DIR`. */
|
|
120
|
+
export declare function clientBuildDir(): string;
|
|
121
|
+
export interface ClientBuildOptions {
|
|
122
|
+
/** Absolute output directory. Default: `<cwd>/.pylon/client-build`. */
|
|
123
|
+
outdir?: string;
|
|
124
|
+
/** The manifest's `outdir` value. Default: `outdir` relative to cwd. A
|
|
125
|
+
* `pylon build` artifact sets `client`, its path at run time. */
|
|
126
|
+
manifestOutdir?: string;
|
|
127
|
+
/** The `build` block to apply. Default: read from `pylon.manifest.json`,
|
|
128
|
+
* except in dev (`NODE_ENV=development`), which applies none. `null`
|
|
129
|
+
* applies none. */
|
|
130
|
+
buildConfig?: ManifestBuildConfig | null;
|
|
131
|
+
/** Fail on a CSS or font build error instead of shipping without it. */
|
|
132
|
+
strict?: boolean;
|
|
133
|
+
/** The app manifest (fonts, build settings). Default
|
|
134
|
+
* `<cwd>/pylon.manifest.json`. */
|
|
135
|
+
manifestPath?: string;
|
|
136
|
+
}
|
|
137
|
+
/** The `build` block of `<cwd>/pylon.manifest.json`, if any. */
|
|
138
|
+
export declare function readManifestBuildConfig(fs: any, path: any, cwd: string): ManifestBuildConfig | undefined;
|
|
111
139
|
/**
|
|
112
140
|
* Compile `app/globals.css` through Tailwind v4 (`@tailwindcss/cli`)
|
|
113
141
|
* if both are present. Returns the relative output path (under
|
|
@@ -115,8 +143,8 @@ export declare function buildClientBundle(appDirRel?: string): Promise<BuildOutp
|
|
|
115
143
|
* project hasn't opted in to Tailwind — we don't want every SSR
|
|
116
144
|
* project to need Tailwind installed.
|
|
117
145
|
*/
|
|
118
|
-
export declare function buildTailwind(fs: any, path: any, cwd: string, outdir: string, appDirRel: string): Promise<string | null>;
|
|
119
|
-
export declare function _doBuildInner(fs: any, path: any, cwd: string, appDirRel: string): Promise<BuildOutput>;
|
|
146
|
+
export declare function buildTailwind(fs: any, path: any, cwd: string, outdir: string, appDirRel: string, cssTargets?: string[]): Promise<string | null>;
|
|
147
|
+
export declare function _doBuildInner(fs: any, path: any, cwd: string, appDirRel: string, opts?: ClientBuildOptions): Promise<BuildOutput>;
|
|
120
148
|
/**
|
|
121
149
|
* Return the bundle manifest. If a fresh manifest exists on disk,
|
|
122
150
|
* use it (caching parse output across requests). Otherwise build
|
package/dist/ssr-fonts.d.ts
CHANGED
|
@@ -90,5 +90,5 @@ export declare function parseGoogleFontsCss(css: string, subsets: string[]): Fac
|
|
|
90
90
|
export declare function buildFonts(fs: any, path: any, cwd: string, outdir: string, fonts: ManifestFontInput[]): Promise<ManifestFonts | null>;
|
|
91
91
|
/** Read the `fonts` array from the app's `pylon.manifest.json` (written next to
|
|
92
92
|
* app.ts). Returns [] when absent/unreadable. */
|
|
93
|
-
export declare function readManifestFonts(fs: any, path: any, cwd: string): ManifestFontInput[];
|
|
93
|
+
export declare function readManifestFonts(fs: any, path: any, cwd: string, manifestPath?: string): ManifestFontInput[];
|
|
94
94
|
export {};
|
package/dist/ssr-runtime.d.ts
CHANGED
|
@@ -313,8 +313,17 @@ export interface SsrMetadata {
|
|
|
313
313
|
* there's no manual XSS handling. Returns null when there's nothing to emit.
|
|
314
314
|
*/
|
|
315
315
|
export declare function renderMetadata(React: any, m: SsrMetadata | undefined): any;
|
|
316
|
-
/**
|
|
316
|
+
/**
|
|
317
|
+
* Import a project-relative module, trying each common extension. A
|
|
318
|
+
* production server bundle serves it from the bundle's module registry.
|
|
319
|
+
*/
|
|
317
320
|
export declare function importModule(cwd: string, relPath: string): Promise<any>;
|
|
321
|
+
/**
|
|
322
|
+
* True when the project has a module at `<dir>/<name>` with any code
|
|
323
|
+
* extension. `dir` is project-relative and "/"-separated. A production server
|
|
324
|
+
* bundle answers from its module registry; source mode checks the disk.
|
|
325
|
+
*/
|
|
326
|
+
export declare function moduleExistsIn(fs: any, path: any, cwd: string, dir: string, name: string): boolean;
|
|
318
327
|
/**
|
|
319
328
|
* `findBoundary` with its filesystem and root injected, so the walk is
|
|
320
329
|
* testable against a fixture without `chdir` — which races every other test
|
|
@@ -442,6 +451,9 @@ export declare function buildHydrationTail(args: {
|
|
|
442
451
|
} | null;
|
|
443
452
|
publicPrefix: string;
|
|
444
453
|
manifestErr: string | null;
|
|
454
|
+
/** core-js polyfill bundle (relative to `publicPrefix`), from the bundle
|
|
455
|
+
* manifest. Loaded before the route entry. */
|
|
456
|
+
polyfills?: string;
|
|
445
457
|
kind?: "error" | "not-found";
|
|
446
458
|
errorForClient?: {
|
|
447
459
|
message: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect console.* from user code to stderr so handlers can't accidentally
|
|
3
|
+
* emit a line that looks like a protocol frame and confuse the Rust reader.
|
|
4
|
+
*
|
|
5
|
+
* Before this guard, a handler calling `console.log('{"type":"return",...}')`
|
|
6
|
+
* — either intentionally or by logging an object shaped that way — would be
|
|
7
|
+
* parsed by the host as a real protocol message. Moving all console output
|
|
8
|
+
* to stderr keeps stdout reserved for NDJSON protocol frames only.
|
|
9
|
+
*
|
|
10
|
+
* The original console methods are saved on the console object as
|
|
11
|
+
* `__stdoutLog` etc. in case the runtime itself needs to write diagnostics
|
|
12
|
+
* to stdout for some reason (it currently doesn't).
|
|
13
|
+
*
|
|
14
|
+
* Safe to call more than once. A production server bundle calls it from its
|
|
15
|
+
* entry, before the app modules load, and the runtime's `main()` calls it
|
|
16
|
+
* again.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fenceStdout(): void;
|
package/dist/types.d.ts
CHANGED
|
@@ -731,6 +731,75 @@ export interface Files {
|
|
|
731
731
|
ttlSecs?: number;
|
|
732
732
|
}): Promise<string>;
|
|
733
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Shard tickets: short-lived, signed permission to join one realtime shard,
|
|
736
|
+
* with claims the shard's authorization hooks read without a database
|
|
737
|
+
* call. Check what the user may do here, then mint:
|
|
738
|
+
*
|
|
739
|
+
* ```ts
|
|
740
|
+
* export default mutation({
|
|
741
|
+
* args: { characterId: v.id("Character") },
|
|
742
|
+
* async handler(ctx, args) {
|
|
743
|
+
* const c = await ctx.db.get("Character", args.characterId);
|
|
744
|
+
* if (c?.ownerId !== ctx.auth.userId) throw ctx.error("FORBIDDEN", "not your character");
|
|
745
|
+
* const shard = `zone-${c.zone}`;
|
|
746
|
+
* const ticket = await ctx.shards.ticket(shard, {
|
|
747
|
+
* subscriberId: c.id,
|
|
748
|
+
* claims: { character: c.id, realm: c.realm },
|
|
749
|
+
* });
|
|
750
|
+
* return { shard, ticket };
|
|
751
|
+
* },
|
|
752
|
+
* });
|
|
753
|
+
* ```
|
|
754
|
+
*
|
|
755
|
+
* The client passes the ticket when it connects (`connectShard(shard, {
|
|
756
|
+
* ticket })`). The shard rejects a ticket for another shard, another
|
|
757
|
+
* subscriber id, or past its expiry.
|
|
758
|
+
*/
|
|
759
|
+
export interface Shards {
|
|
760
|
+
/**
|
|
761
|
+
* Mint a ticket for `shardId`. `subscriberId` defaults to the calling
|
|
762
|
+
* user's id; `ttlSecs` defaults to 60 and is capped at 3600 by the host.
|
|
763
|
+
*/
|
|
764
|
+
ticket(shardId: string, opts?: {
|
|
765
|
+
subscriberId?: string;
|
|
766
|
+
claims?: Record<string, unknown>;
|
|
767
|
+
ttlSecs?: number;
|
|
768
|
+
}): Promise<string>;
|
|
769
|
+
/**
|
|
770
|
+
* Start shard `shardId` of a kind declared with `shard({...})` in app.ts.
|
|
771
|
+
* `params` reach the module's `init`. Actions only: a mutation's
|
|
772
|
+
* rollback cannot undo it.
|
|
773
|
+
*
|
|
774
|
+
* Throws `SHARD_EXISTS` when the id is running, `SHARD_LIMIT_REACHED`
|
|
775
|
+
* at the kind's `maxInstances`, `SHARD_KIND_NOT_FOUND`,
|
|
776
|
+
* `SHARD_ID_INVALID`, or `SHARD_INIT_FAILED` when `init` refuses.
|
|
777
|
+
*/
|
|
778
|
+
create(kind: string, shardId: string, params?: unknown): Promise<ShardInfo>;
|
|
779
|
+
/** Stop a shard and close its subscribers' connections. Resolves to
|
|
780
|
+
* `false` when no shard has that id. Actions only. */
|
|
781
|
+
stop(shardId: string): Promise<boolean>;
|
|
782
|
+
/** A running shard, or `null`. */
|
|
783
|
+
get(shardId: string): Promise<ShardInfo | null>;
|
|
784
|
+
/** Every running shard. */
|
|
785
|
+
list(): Promise<ShardInfo[]>;
|
|
786
|
+
}
|
|
787
|
+
/** `ctx.shards` in a query or mutation: tickets and reads, no start or stop. */
|
|
788
|
+
export type ShardsReader = Pick<Shards, "ticket" | "get" | "list">;
|
|
789
|
+
/** A running shard, from `ctx.shards.create`, `get`, or `list`. */
|
|
790
|
+
export interface ShardInfo {
|
|
791
|
+
id: string;
|
|
792
|
+
/** The shard kind's name. */
|
|
793
|
+
kind: string;
|
|
794
|
+
/** Ticks run so far. */
|
|
795
|
+
tick: number;
|
|
796
|
+
subscribers: number;
|
|
797
|
+
/** False once the shard stopped (finished, idle, or failed) and before
|
|
798
|
+
* the host removes it. */
|
|
799
|
+
running: boolean;
|
|
800
|
+
/** Why the module stopped, when it trapped. */
|
|
801
|
+
error?: string;
|
|
802
|
+
}
|
|
734
803
|
/** Context for query handlers (read-only).
|
|
735
804
|
*
|
|
736
805
|
* NOTE: `ctx.llm` is NOT exposed here. Queries are reactive: a
|
|
@@ -756,6 +825,8 @@ export interface QueryCtx<R extends AuthRequirement = "optional"> {
|
|
|
756
825
|
requireMember: RequireMember;
|
|
757
826
|
/** Signed file-download URLs — see {@link Files}. */
|
|
758
827
|
files: Files;
|
|
828
|
+
/** Shard tickets and reads — see {@link Shards}. */
|
|
829
|
+
shards: ShardsReader;
|
|
759
830
|
/**
|
|
760
831
|
* Fires when the host cancels this call (idle timeout exceeded).
|
|
761
832
|
* Thread it into `fetch(url, { signal: ctx.signal })` or SDK calls so
|
|
@@ -783,6 +854,8 @@ export interface MutationCtx<R extends AuthRequirement = "optional"> {
|
|
|
783
854
|
workflows: Workflows;
|
|
784
855
|
/** Signed file-download URLs — see {@link Files}. */
|
|
785
856
|
files: Files;
|
|
857
|
+
/** Shard tickets and reads — see {@link Shards}. */
|
|
858
|
+
shards: ShardsReader;
|
|
786
859
|
/** Create a typed error that triggers rollback. */
|
|
787
860
|
error(code: string, message: string): Error;
|
|
788
861
|
/** Assert org membership (optionally a role) — see {@link RequireMember}. */
|
|
@@ -952,6 +1025,8 @@ export interface ActionCtx<R extends AuthRequirement = "optional"> {
|
|
|
952
1025
|
env: Record<string, string>;
|
|
953
1026
|
/** Signed file-download URLs — see {@link Files}. */
|
|
954
1027
|
files: Files;
|
|
1028
|
+
/** Shard tickets, reads, and start/stop — see {@link Shards}. */
|
|
1029
|
+
shards: Shards;
|
|
955
1030
|
/** Run a registered query within its own read transaction. */
|
|
956
1031
|
runQuery<T = unknown>(fnName: string, args: Record<string, unknown>): Promise<T>;
|
|
957
1032
|
/** Run a registered mutation within its own write transaction. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylonsync/functions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "TypeScript function runtime for pylon — defines server-side queries, mutations, and actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -52,11 +52,15 @@
|
|
|
52
52
|
"typescript": "^5.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"
|
|
56
|
-
"react-dom": "^19.0.0",
|
|
55
|
+
"@swc/core": "^1.16.2",
|
|
57
56
|
"@types/react": "^19.0.0",
|
|
58
57
|
"@types/react-dom": "^19.0.0",
|
|
59
|
-
"
|
|
58
|
+
"browserslist": "^4.29.1",
|
|
59
|
+
"core-js": "^3.50.0",
|
|
60
|
+
"happy-dom": "^15.0.0",
|
|
61
|
+
"lightningcss": "~1.32.0",
|
|
62
|
+
"react": "^19.0.0",
|
|
63
|
+
"react-dom": "^19.0.0"
|
|
60
64
|
},
|
|
61
65
|
"peerDependencies": {
|
|
62
66
|
"bun-types": "*"
|
package/src/build-cli.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Entry the Rust CLI runs for `pylon build`:
|
|
2
|
+
//
|
|
3
|
+
// bun run <@pylonsync/functions>/src/build-cli.ts --out <dir>
|
|
4
|
+
// [--app-dir app] [--manifest <path to pylon.manifest.json>]
|
|
5
|
+
//
|
|
6
|
+
// Writes the artifact (see production-build.ts) and prints one JSON line with
|
|
7
|
+
// the result on stdout. Errors go to stderr with a non-zero exit.
|
|
8
|
+
|
|
9
|
+
import { buildProduction } from "./production-build";
|
|
10
|
+
|
|
11
|
+
function flag(name: string): string | undefined {
|
|
12
|
+
const i = process.argv.indexOf(name);
|
|
13
|
+
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// The React production build, and the same NODE_ENV the server bundle
|
|
17
|
+
// defines. Set before the bundler reads it.
|
|
18
|
+
process.env.NODE_ENV = "production";
|
|
19
|
+
|
|
20
|
+
const path = await import("node:path");
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const out = flag("--out") ?? "dist";
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await buildProduction({
|
|
26
|
+
cwd,
|
|
27
|
+
outDir: path.resolve(cwd, out),
|
|
28
|
+
appDir: flag("--app-dir") ?? "app",
|
|
29
|
+
manifestPath: flag("--manifest"),
|
|
30
|
+
functionsDir: process.env.PYLON_FUNCTIONS_DIR ?? "functions",
|
|
31
|
+
});
|
|
32
|
+
process.stdout.write(JSON.stringify({ ok: true, ...result }) + "\n");
|
|
33
|
+
} catch (e: any) {
|
|
34
|
+
process.stderr.write(`${e?.message ?? String(e)}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Client browser compatibility (build.target / polyfill / css.target).
|
|
2
|
+
//
|
|
3
|
+
// The pure helpers are tested directly. `applyClientCompat` and
|
|
4
|
+
// `transformCss` run the real tools (SWC, browserslist, core-js, Lightning
|
|
5
|
+
// CSS), resolved from this package's devDependencies, against files in a
|
|
6
|
+
// temp dir.
|
|
7
|
+
|
|
8
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
applyClientCompat,
|
|
15
|
+
browsersBelowFloor,
|
|
16
|
+
extractCoreJsImports,
|
|
17
|
+
rehashName,
|
|
18
|
+
resolveClientCompat,
|
|
19
|
+
targetQueries,
|
|
20
|
+
transformCss,
|
|
21
|
+
} from "./build-compat";
|
|
22
|
+
|
|
23
|
+
const PKG_DIR = path.resolve(import.meta.dir, "..");
|
|
24
|
+
let tmp: string | null = null;
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
if (tmp) fs.rmSync(tmp, { recursive: true, force: true });
|
|
27
|
+
tmp = null;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("targetQueries", () => {
|
|
31
|
+
test("an ECMAScript edition becomes browser versions", () => {
|
|
32
|
+
expect(targetQueries("es2019", "build.target")).toContain("safari 12.1");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("browserslist queries pass through", () => {
|
|
36
|
+
expect(targetQueries(["safari >= 14", "chrome >= 90"], "t")).toEqual([
|
|
37
|
+
"safari >= 14",
|
|
38
|
+
"chrome >= 90",
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("editions below es2018 are rejected: the runtime needs ES modules", () => {
|
|
43
|
+
expect(() => targetQueries("es2015", "build.target")).toThrow("es2018");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("empty values are rejected", () => {
|
|
47
|
+
expect(() => targetQueries([], "build.target")).toThrow("must not be empty");
|
|
48
|
+
expect(() => targetQueries([" "], "build.target")).toThrow("non-empty");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("resolveClientCompat", () => {
|
|
53
|
+
test("no target and no css target → no compat pass", () => {
|
|
54
|
+
expect(resolveClientCompat(undefined)).toBeNull();
|
|
55
|
+
expect(resolveClientCompat({ server: { external: ["x"] } })).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("css.target defaults to target", () => {
|
|
59
|
+
const c = resolveClientCompat({ target: "es2020" })!;
|
|
60
|
+
expect(c.cssTargets).toEqual(c.jsTargets);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("css.target alone lowers CSS only", () => {
|
|
64
|
+
const c = resolveClientCompat({ css: { target: "safari >= 13" } })!;
|
|
65
|
+
expect(c.jsTargets).toEqual([]);
|
|
66
|
+
expect(c.cssTargets).toEqual(["safari >= 13"]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("polyfill without target is an error", () => {
|
|
70
|
+
expect(() => resolveClientCompat({ polyfill: "usage" })).toThrow("needs build.target");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("extractCoreJsImports strips minified and spaced forms", () => {
|
|
75
|
+
const found = new Set<string>();
|
|
76
|
+
const code =
|
|
77
|
+
'import"core-js/modules/es.array.at.js";import "core-js/modules/es.object.has-own.js";\nconst a=1;';
|
|
78
|
+
expect(extractCoreJsImports(code, found).trim()).toBe("const a=1;");
|
|
79
|
+
expect([...found].sort()).toEqual([
|
|
80
|
+
"core-js/modules/es.array.at.js",
|
|
81
|
+
"core-js/modules/es.object.has-own.js",
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("rehashName changes the hash with the salt and keeps the stem", () => {
|
|
86
|
+
const h = (s: string) => Bun.hash(s).toString(16).padStart(16, "0");
|
|
87
|
+
const a = rehashName("client-entry-app__page-abc123.js", "es2019", h);
|
|
88
|
+
const b = rehashName("client-entry-app__page-abc123.js", "es2020", h);
|
|
89
|
+
expect(a).toMatch(/^client-entry-app__page-[0-9a-f]{10}\.js$/);
|
|
90
|
+
expect(a).not.toBe(b);
|
|
91
|
+
expect(rehashName("loro_wasm_bg.wasm", "x", h)).toBe("loro_wasm_bg.wasm");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("browsersBelowFloor lists browsers that cannot load ES module entries", () => {
|
|
95
|
+
expect(browsersBelowFloor(["chrome 90", "safari 10.1", "ie 11", "ios_saf 15.2-15.3"])).toEqual([
|
|
96
|
+
"safari 10.1",
|
|
97
|
+
"ie 11",
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("applyClientCompat (real SWC + core-js)", () => {
|
|
102
|
+
function outdirWith(files: Record<string, string>): string {
|
|
103
|
+
// Inside the package so the tools resolve from its node_modules.
|
|
104
|
+
tmp = fs.mkdtempSync(path.join(PKG_DIR, ".compat-test-"));
|
|
105
|
+
const out = path.join(tmp, "client-build");
|
|
106
|
+
for (const [rel, code] of Object.entries(files)) {
|
|
107
|
+
fs.mkdirSync(path.dirname(path.join(out, rel)), { recursive: true });
|
|
108
|
+
fs.writeFileSync(path.join(out, rel), code);
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
test("lowers syntax, bundles usage polyfills, and renames every output", async () => {
|
|
114
|
+
const out = outdirWith({
|
|
115
|
+
"client-entry-app__page-aaaa1111.js":
|
|
116
|
+
'import{x as t}from"./chunks/shared-bbbb2222.js";export const v=t?.y??[1].at(-1);',
|
|
117
|
+
"chunks/shared-bbbb2222.js": "export const x={y:Object.hasOwn({},'a')};",
|
|
118
|
+
});
|
|
119
|
+
const res = await applyClientCompat({
|
|
120
|
+
fs,
|
|
121
|
+
path,
|
|
122
|
+
cwd: tmp!,
|
|
123
|
+
outdir: out,
|
|
124
|
+
jsFiles: ["client-entry-app__page-aaaa1111.js", "chunks/shared-bbbb2222.js"],
|
|
125
|
+
compat: {
|
|
126
|
+
jsTargets: ["chrome 70", "safari 12"],
|
|
127
|
+
cssTargets: [],
|
|
128
|
+
polyfill: "usage",
|
|
129
|
+
sourcemap: false,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const entryRel = res.renamed.get("client-entry-app__page-aaaa1111.js")!;
|
|
134
|
+
const chunkRel = res.renamed.get("chunks/shared-bbbb2222.js")!;
|
|
135
|
+
expect(entryRel).toMatch(/^client-entry-app__page-[0-9a-f]{10}\.js$/);
|
|
136
|
+
expect(chunkRel).toMatch(/^chunks\/shared-[0-9a-f]{10}\.js$/);
|
|
137
|
+
expect(fs.existsSync(path.join(out, "client-entry-app__page-aaaa1111.js"))).toBe(false);
|
|
138
|
+
|
|
139
|
+
const entry = fs.readFileSync(path.join(out, entryRel), "utf8");
|
|
140
|
+
expect(entry).not.toContain("?.");
|
|
141
|
+
expect(entry).not.toContain("??");
|
|
142
|
+
expect(entry).not.toContain("core-js");
|
|
143
|
+
// The reference to the renamed chunk was rewritten.
|
|
144
|
+
expect(entry).toContain(path.basename(chunkRel));
|
|
145
|
+
expect(entry).not.toContain("shared-bbbb2222.js");
|
|
146
|
+
|
|
147
|
+
expect(res.polyfills).toMatch(/^polyfills-[0-9a-f]{10}\.js$/);
|
|
148
|
+
const poly = fs.readFileSync(path.join(out, res.polyfills!), "utf8");
|
|
149
|
+
expect(poly.length).toBeGreaterThan(1000);
|
|
150
|
+
expect(poly).not.toMatch(/^import/m);
|
|
151
|
+
expect(res.unsupported).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("targets that need no polyfill produce no polyfill file", async () => {
|
|
155
|
+
const out = outdirWith({ "client-entry-app__page-cccc3333.js": "export const v=1+1;" });
|
|
156
|
+
const res = await applyClientCompat({
|
|
157
|
+
fs,
|
|
158
|
+
path,
|
|
159
|
+
cwd: tmp!,
|
|
160
|
+
outdir: out,
|
|
161
|
+
jsFiles: ["client-entry-app__page-cccc3333.js"],
|
|
162
|
+
compat: { jsTargets: ["chrome 120"], cssTargets: [], polyfill: "usage", sourcemap: false },
|
|
163
|
+
});
|
|
164
|
+
expect(res.polyfills).toBeUndefined();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("transformCss adds prefixes and lowers nesting for the targets", async () => {
|
|
169
|
+
const css = ".a{user-select:none;&:hover{color:red}}";
|
|
170
|
+
const out = await transformCss(PKG_DIR, css, "x.css", ["safari 13"]);
|
|
171
|
+
expect(out).toContain("-webkit-user-select:none");
|
|
172
|
+
expect(out).toContain(".a:hover");
|
|
173
|
+
});
|