@sigmx/astro 0.1.0 → 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.
package/dist/client.js CHANGED
@@ -1,6 +1,3 @@
1
- // Convenience re-exports for app-local entrypoints:
2
- // import { createSigmx, all, plugins } from '@sigmx/astro/client'
3
- // createSigmx({ plugins: [plugins.text, plugins.on] })
4
1
  export * from 'sigmx';
5
2
  export * as plugins from 'sigmx/plugins';
6
3
  export { all } from 'sigmx/presets/all';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { sigmxAuto, sigmxPrecompile } from 'sigmx/vite';
1
+ import { sigmxAuto, sigmxPrecompile, } from 'sigmx/vite';
2
2
  const AUTO = 'virtual:sigmx-plugins';
3
3
  const EXPRESSIONS = 'virtual:sigmx-expressions';
4
4
  /** Source of the injected client module. Exported for tests and custom entrypoints. */
@@ -12,7 +12,9 @@ export const bootScript = (o = {}) => {
12
12
  const expose = o.expose === false ? '' : `\nwindow[${JSON.stringify(o.expose ?? 'sigmx')}] = app;`;
13
13
  if (!o.precompile)
14
14
  return `import { createSigmx } from 'sigmx';\n${imports}\nconst app = createSigmx({ plugins, ...${options} });${expose}\n`;
15
- const fallback = typeof o.precompile === 'object' && o.precompile.fallback === false ? 'undefined' : 'runtimeExpressions(functionCompiler)';
15
+ const fallback = typeof o.precompile === 'object' && o.precompile.fallback === false
16
+ ? 'undefined'
17
+ : 'runtimeExpressions(functionCompiler)';
16
18
  return (`import { createRuntime, precompiled, runtimeExpressions, functionCompiler } from 'sigmx';\nimport { table } from '${EXPRESSIONS}';\n${imports}\n` +
17
19
  `const app = createRuntime({ plugins, expressions: precompiled(table, ${fallback}), ...${options} });${expose}\n`);
18
20
  };
@@ -28,7 +30,13 @@ export default function sigmx(options = {}) {
28
30
  plugins.push(sigmxAuto({ ...options.auto, root, prefixes }));
29
31
  if (options.precompile) {
30
32
  const po = typeof options.precompile === 'object' ? options.precompile : {};
31
- plugins.push(sigmxPrecompile({ include: po.include, extensions: po.extensions, custom: po.custom ?? options.auto?.custom, root, prefixes }));
33
+ plugins.push(sigmxPrecompile({
34
+ include: po.include,
35
+ extensions: po.extensions,
36
+ custom: po.custom ?? options.auto?.custom,
37
+ root,
38
+ prefixes,
39
+ }));
32
40
  }
33
41
  if (plugins.length)
34
42
  updateConfig({ vite: { plugins } });
package/dist/server.d.ts CHANGED
@@ -1,106 +1 @@
1
- /** Query-string key that carries signals on GET/DELETE requests. */
2
- export declare const SIGNALS_KEY = "sigmx";
3
- export declare const EVENT_PATCH_ELEMENTS = "sigmx-patch-elements";
4
- export declare const EVENT_PATCH_SIGNALS = "sigmx-patch-signals";
5
- export declare const SSE_HEADERS: {
6
- readonly 'content-type': "text/event-stream";
7
- readonly 'cache-control': "no-cache";
8
- readonly connection: "keep-alive";
9
- };
10
- export type PatchMode = 'outer' | 'inner' | 'replace' | 'prepend' | 'append' | 'before' | 'after' | 'remove';
11
- export interface ServerEvent {
12
- event: string;
13
- /** Data lines, each `key value`. */
14
- lines: string[];
15
- id?: string;
16
- retry?: number;
17
- }
18
- export interface EventOptions {
19
- id?: string;
20
- retry?: number;
21
- }
22
- export interface PatchElementsOptions extends EventOptions {
23
- selector?: string;
24
- mode?: PatchMode;
25
- useViewTransition?: boolean;
26
- }
27
- export interface PatchSignalsOptions extends EventOptions {
28
- onlyIfMissing?: boolean;
29
- }
30
- export interface ExecuteScriptOptions extends EventOptions {
31
- /** Remove the script element after it runs (default true). */
32
- autoRemove?: boolean;
33
- attributes?: Record<string, string>;
34
- }
35
- /** Patch HTML into the page. Without a selector, top-level elements are morphed by id. */
36
- export declare const patchElements: (html: string, o?: PatchElementsOptions) => ServerEvent;
37
- export declare const removeElements: (selector: string, o?: EventOptions) => ServerEvent;
38
- /** Merge signals (JSON merge-patch: null removes). Accepts an object or pre-serialised JSON. */
39
- export declare const patchSignals: (signals: Record<string, unknown> | string, o?: PatchSignalsOptions) => ServerEvent;
40
- /** Remove signals by dotted path. */
41
- export declare const removeSignals: (paths: string[], o?: EventOptions) => ServerEvent;
42
- /** Run JavaScript in the page by appending a script element to the body. */
43
- export declare const executeScript: (script: string, o?: ExecuteScriptOptions) => ServerEvent;
44
- /** Serialise one event in `text/event-stream` format. */
45
- export declare const formatEvent: (e: ServerEvent) => string;
46
- /** A streaming event-source response you write to over time. */
47
- export declare class SigmxStream {
48
- readonly response: Response;
49
- private controller;
50
- private readonly encoder;
51
- private open;
52
- constructor(init?: ResponseInit);
53
- get closed(): boolean;
54
- send(event: ServerEvent): this;
55
- patchElements(html: string, o?: PatchElementsOptions): this;
56
- removeElements(selector: string, o?: EventOptions): this;
57
- patchSignals(signals: Record<string, unknown> | string, o?: PatchSignalsOptions): this;
58
- removeSignals(paths: string[], o?: EventOptions): this;
59
- executeScript(script: string, o?: ExecuteScriptOptions): this;
60
- close(): void;
61
- }
62
- /** A complete event-source response from a fixed list of events. */
63
- export declare const sse: (...events: ServerEvent[]) => Response;
64
- /** Stream events while `fn` runs; the response closes when it resolves. */
65
- export declare const sseStream: (fn: (stream: SigmxStream) => Promise<void> | void, init?: ResponseInit) => Response;
66
- /** A plain HTML response; the client morphs it by id, or honours the selector and mode headers. */
67
- export declare const html: (body: string, o?: PatchElementsOptions & {
68
- status?: number;
69
- }) => Response;
70
- /** A plain JSON response; the client merges it as signals. */
71
- export declare const json: (signals: Record<string, unknown>, o?: PatchSignalsOptions & {
72
- status?: number;
73
- }) => Response;
74
- /** Minimal Standard Schema (standardschema.dev) so any compliant validator works without a dependency. */
75
- export interface StandardSchema<Output = unknown> {
76
- readonly '~standard': {
77
- readonly validate: (value: unknown) => SchemaResult<Output> | Promise<SchemaResult<Output>>;
78
- };
79
- }
80
- export type SchemaIssue = {
81
- readonly message: string;
82
- readonly path?: ReadonlyArray<PropertyKey | {
83
- readonly key: PropertyKey;
84
- }>;
85
- };
86
- export type SchemaResult<T> = {
87
- readonly value: T;
88
- readonly issues?: undefined;
89
- } | {
90
- readonly issues: ReadonlyArray<SchemaIssue>;
91
- };
92
- export declare class SignalsError extends Error {
93
- readonly issues: ReadonlyArray<SchemaIssue>;
94
- readonly status: number;
95
- constructor(message: string, issues?: ReadonlyArray<SchemaIssue>, status?: number);
96
- /** A ready-made error response for endpoints that want to bail out. */
97
- response(): Response;
98
- }
99
- /**
100
- * Read the signals a sigmx request carries: the `sigmx` query parameter on GET/DELETE, a JSON
101
- * body otherwise, or form fields for `contentType: 'form'` requests. Validates with `schema` if given.
102
- */
103
- export declare function readSignals(request: Request): Promise<Record<string, any>>;
104
- export declare function readSignals<T>(request: Request, schema: StandardSchema<T>): Promise<T>;
105
- /** True when the request was made by the sigmx client (has the `Sigmx-Request` header). */
106
- export declare const isSigmxRequest: (request: Request) => boolean;
1
+ export * from 'sigmx/server';
package/dist/server.js CHANGED
@@ -1,181 +1,3 @@
1
- // Server helpers for answering sigmx requests from Astro endpoints (or any Fetch-API runtime).
2
- /** Query-string key that carries signals on GET/DELETE requests. */
3
- export const SIGNALS_KEY = 'sigmx';
4
- export const EVENT_PATCH_ELEMENTS = 'sigmx-patch-elements';
5
- export const EVENT_PATCH_SIGNALS = 'sigmx-patch-signals';
6
- export const SSE_HEADERS = {
7
- 'content-type': 'text/event-stream',
8
- 'cache-control': 'no-cache',
9
- connection: 'keep-alive',
10
- };
11
- // ---- event builders -------------------------------------------------------
12
- /** Patch HTML into the page. Without a selector, top-level elements are morphed by id. */
13
- export const patchElements = (html, o = {}) => {
14
- const lines = [];
15
- if (o.selector)
16
- lines.push(`selector ${o.selector}`);
17
- if (o.mode && o.mode !== 'outer')
18
- lines.push(`mode ${o.mode}`);
19
- if (o.useViewTransition)
20
- lines.push('useViewTransition true');
21
- for (const l of html.trim().split('\n'))
22
- lines.push(`elements ${l}`);
23
- return { event: EVENT_PATCH_ELEMENTS, lines, id: o.id, retry: o.retry };
24
- };
25
- export const removeElements = (selector, o = {}) => ({
26
- event: EVENT_PATCH_ELEMENTS,
27
- lines: [`selector ${selector}`, 'mode remove'],
28
- ...o,
29
- });
30
- /** Merge signals (JSON merge-patch: null removes). Accepts an object or pre-serialised JSON. */
31
- export const patchSignals = (signals, o = {}) => ({
32
- event: EVENT_PATCH_SIGNALS,
33
- lines: [...(o.onlyIfMissing ? ['onlyIfMissing true'] : []), `signals ${typeof signals === 'string' ? signals : JSON.stringify(signals)}`],
34
- id: o.id,
35
- retry: o.retry,
36
- });
37
- /** Remove signals by dotted path. */
38
- export const removeSignals = (paths, o = {}) => {
39
- const patch = {};
40
- for (const p of paths) {
41
- const keys = p.split('.');
42
- const last = keys.pop();
43
- let cur = patch;
44
- for (const k of keys)
45
- cur = cur[k] ??= {};
46
- cur[last] = null;
47
- }
48
- return patchSignals(patch, o);
49
- };
50
- /** Run JavaScript in the page by appending a script element to the body. */
51
- export const executeScript = (script, o = {}) => {
52
- const attrs = { ...(o.autoRemove ?? true ? { 'data-init': 'el.remove()' } : {}), ...o.attributes };
53
- const attrText = Object.entries(attrs)
54
- .map(([k, v]) => ` ${k}="${v.replace(/"/g, '&quot;')}"`)
55
- .join('');
56
- return patchElements(`<script${attrText}>${script}</script>`, { selector: 'body', mode: 'append', id: o.id, retry: o.retry });
57
- };
58
- /** Serialise one event in `text/event-stream` format. */
59
- export const formatEvent = (e) => [e.id !== undefined && `id: ${e.id}`, e.retry !== undefined && `retry: ${e.retry}`, `event: ${e.event}`, ...e.lines.map((l) => `data: ${l}`)]
60
- .filter(Boolean)
61
- .join('\n') + '\n\n';
62
- // ---- responses ------------------------------------------------------------
63
- /** A streaming event-source response you write to over time. */
64
- export class SigmxStream {
65
- response;
66
- controller;
67
- encoder = new TextEncoder();
68
- open = true;
69
- constructor(init = {}) {
70
- const body = new ReadableStream({
71
- start: (c) => {
72
- this.controller = c;
73
- },
74
- cancel: () => {
75
- this.open = false;
76
- },
77
- });
78
- this.response = new Response(body, { ...init, headers: { ...SSE_HEADERS, ...init.headers } });
79
- }
80
- get closed() {
81
- return !this.open;
82
- }
83
- send(event) {
84
- if (this.open)
85
- this.controller.enqueue(this.encoder.encode(formatEvent(event)));
86
- return this;
87
- }
88
- patchElements(html, o) {
89
- return this.send(patchElements(html, o));
90
- }
91
- removeElements(selector, o) {
92
- return this.send(removeElements(selector, o));
93
- }
94
- patchSignals(signals, o) {
95
- return this.send(patchSignals(signals, o));
96
- }
97
- removeSignals(paths, o) {
98
- return this.send(removeSignals(paths, o));
99
- }
100
- executeScript(script, o) {
101
- return this.send(executeScript(script, o));
102
- }
103
- close() {
104
- if (this.open) {
105
- this.open = false;
106
- this.controller.close();
107
- }
108
- }
109
- }
110
- /** A complete event-source response from a fixed list of events. */
111
- export const sse = (...events) => new Response(events.map(formatEvent).join(''), { headers: SSE_HEADERS });
112
- /** Stream events while `fn` runs; the response closes when it resolves. */
113
- export const sseStream = (fn, init) => {
114
- const stream = new SigmxStream(init);
115
- Promise.resolve()
116
- .then(() => fn(stream))
117
- .catch((e) => console.error('sigmx sseStream:', e))
118
- .finally(() => stream.close());
119
- return stream.response;
120
- };
121
- /** A plain HTML response; the client morphs it by id, or honours the selector and mode headers. */
122
- export const html = (body, o = {}) => new Response(body, {
123
- status: o.status ?? 200,
124
- headers: {
125
- 'content-type': 'text/html; charset=utf-8',
126
- ...(o.selector ? { 'sigmx-selector': o.selector } : {}),
127
- ...(o.mode ? { 'sigmx-mode': o.mode } : {}),
128
- ...(o.useViewTransition ? { 'sigmx-use-view-transition': 'true' } : {}),
129
- },
130
- });
131
- /** A plain JSON response; the client merges it as signals. */
132
- export const json = (signals, o = {}) => new Response(JSON.stringify(signals), {
133
- status: o.status ?? 200,
134
- headers: { 'content-type': 'application/json', ...(o.onlyIfMissing ? { 'sigmx-only-if-missing': 'true' } : {}) },
135
- });
136
- export class SignalsError extends Error {
137
- issues;
138
- status;
139
- constructor(message, issues = [], status = 422) {
140
- super(message);
141
- this.issues = issues;
142
- this.status = status;
143
- this.name = 'SignalsError';
144
- }
145
- /** A ready-made error response for endpoints that want to bail out. */
146
- response() {
147
- return Response.json({ error: this.message, issues: this.issues }, { status: this.status });
148
- }
149
- }
150
- export async function readSignals(request, schema) {
151
- let raw;
152
- const method = request.method.toUpperCase();
153
- const type = request.headers.get('content-type') ?? '';
154
- try {
155
- if (method === 'GET' || method === 'DELETE') {
156
- const q = new URL(request.url).searchParams.get(SIGNALS_KEY);
157
- raw = q ? JSON.parse(q) : {};
158
- }
159
- else if (type.includes('application/json')) {
160
- const text = await request.text();
161
- raw = text ? JSON.parse(text) : {};
162
- }
163
- else if (type.includes('form')) {
164
- raw = Object.fromEntries((await request.formData()).entries());
165
- }
166
- else {
167
- raw = {};
168
- }
169
- }
170
- catch (e) {
171
- throw new SignalsError(`could not parse signals: ${e.message}`, [], 400);
172
- }
173
- if (!schema)
174
- return raw;
175
- const result = await schema['~standard'].validate(raw);
176
- if (result.issues)
177
- throw new SignalsError(`invalid signals: ${result.issues.map((i) => i.message).join('; ')}`, result.issues);
178
- return result.value;
179
- }
180
- /** True when the request was made by the sigmx client (has the `Sigmx-Request` header). */
181
- export const isSigmxRequest = (request) => request.headers.get('sigmx-request') === 'true';
1
+ // The server helpers live in the core package so every runtime shares them; this entry keeps
2
+ // `@sigmx/astro/server` working and documents that they are plain Fetch-API code.
3
+ export * from 'sigmx/server';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigmx/astro",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Astro integration and server helpers for sigmx: inject the client, stream patches from endpoints, read signals from requests.",
5
5
  "keywords": [
6
6
  "astro",
@@ -61,6 +61,6 @@
61
61
  },
62
62
  "peerDependencies": {
63
63
  "astro": "^5.0.0 || ^6.0.0 || ^7.0.0",
64
- "sigmx": "^0.1.0"
64
+ "sigmx": "^0.2.0"
65
65
  }
66
66
  }