@navette-js/wire 1.0.0-beta.3 → 1.0.0-beta.5

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.
@@ -4,9 +4,10 @@ Navette is an independent project, not affiliated with or endorsed by the Filame
4
4
 
5
5
  | Work | Used for | License | License file |
6
6
  |---|---|---|---|
7
- | [Filament](https://github.com/filamentphp/filament) © Filament | HTML markup derived from its Blade views, compiled CSS and JavaScript (`@navette-js/panels/dist/filament`), English translations (`@navette-js/support/data`) | MIT | `packages/panels/dist/FILAMENT-LICENSE.md` |
7
+ | [Filament](https://github.com/filamentphp/filament) © Filament | HTML markup derived from its Blade views, compiled CSS and JavaScript (`@navette-js/panels/dist/filament`), English translations (`@navette-js/support/data`), image editor icons (`@navette-js/support/icons/filament`) | MIT | `packages/panels/dist/FILAMENT-LICENSE.md`, `packages/support/icons/filament/LICENSE.md` |
8
8
  | [Livewire](https://github.com/livewire/livewire) © Caleb Porzio | Client script `livewire.js` (`@navette-js/wire/vendor`); the server runtime follows its protocol | MIT | `packages/wire/vendor/LIVEWIRE-LICENSE.md` |
9
9
  | [Heroicons](https://github.com/tailwindlabs/heroicons) | SVG icons (`@navette-js/support/icons`) | MIT | `packages/support/icons/HEROICONS-LICENSE.md` |
10
+ | [QR Code generator library](https://www.nayuki.io/page/qr-code-generator-library) © Project Nayuki | Structure of the QR Code encoder (`@navette-js/panels`, `qr-code.ts`) | MIT | header of `packages/panels/src/qr-code.ts` |
10
11
  | [Inter](https://github.com/rsms/inter) © The Inter Project Authors | Default font files (`@navette-js/panels/dist/filament/fonts`) | SIL Open Font License 1.1 | `packages/panels/dist/filament/fonts/INTER-LICENSE.txt` |
11
12
 
12
13
  Runtime dependencies installed from npm (Express, Knex, Objection, better-sqlite3, Tiptap, EJS, Busboy…) keep their own licenses.
package/lib/index.d.ts CHANGED
@@ -76,6 +76,11 @@ export declare abstract class Component {
76
76
  redirect(url: string, navigate?: boolean): void;
77
77
  skipRender(): void;
78
78
  /** Partial render: only the `wire:partial="<name>"` fragment is sent back (effects.partials), the component is not re-rendered. */
79
+ /**
80
+ * JavaScript run once in the browser for this component, with `$wire` in scope (Livewire's `@script`). Call it while rendering;
81
+ * `key` identifies the script: it is sent again only if the browser has not run it yet.
82
+ */
83
+ addScript(key: string, js: string): void;
79
84
  renderPartial(name: string, html: string): void;
80
85
  addError(path: string, message: string): void;
81
86
  resetErrorBag(): void;
@@ -86,6 +91,8 @@ export declare abstract class Component {
86
91
  * forged request and its return value sent back to the browser.
87
92
  */
88
93
  export declare function callableMethods(component: Component): Set<string>;
94
+ /** Properties the browser may not change: those the class or any parent class lists in `static locked`. */
95
+ export declare function lockedProperties(component: Component): Set<string>;
89
96
  export declare function publicProperties(component: Component): string[];
90
97
  export declare function dehydrateValue(value: unknown): unknown;
91
98
  export declare function hydrateValue(value: unknown): unknown;
package/lib/index.js CHANGED
@@ -58,6 +58,11 @@ export class Component {
58
58
  }
59
59
  skipRender() { this._skipRender = true; }
60
60
  /** Partial render: only the `wire:partial="<name>"` fragment is sent back (effects.partials), the component is not re-rendered. */
61
+ /**
62
+ * JavaScript run once in the browser for this component, with `$wire` in scope (Livewire's `@script`). Call it while rendering;
63
+ * `key` identifies the script: it is sent again only if the browser has not run it yet.
64
+ */
65
+ addScript(key, js) { (this._effects.scripts ??= {})[key] = `<script>${js}</script>`; }
61
66
  renderPartial(name, html) { (this._effects.partials ??= {})[name] = html; this._skipRender = true; }
62
67
  addError(path, message) { (this._errors[path] ??= []).push(message); }
63
68
  resetErrorBag() { this._errors = {}; }
@@ -81,6 +86,16 @@ export function callableMethods(component) {
81
86
  }
82
87
  return out;
83
88
  }
89
+ /** Properties the browser may not change: those the class or any parent class lists in `static locked`. */
90
+ export function lockedProperties(component) {
91
+ const out = new Set();
92
+ for (let cls = component.constructor; cls && cls !== Component; cls = Object.getPrototypeOf(cls)) {
93
+ if (Object.prototype.hasOwnProperty.call(cls, 'locked') && Array.isArray(cls.locked))
94
+ for (const name of cls.locked)
95
+ out.add(name);
96
+ }
97
+ return out;
98
+ }
84
99
  export function publicProperties(component) {
85
100
  return Object.keys(component).filter((k) => !k.startsWith('_'));
86
101
  }
@@ -238,7 +253,7 @@ export class WireRuntime {
238
253
  }
239
254
  await component.dehydrate();
240
255
  const snapshot = this.snapshot(component, name, {
241
- path: request.path ?? '/', method: request.method ?? 'GET', release: 'a-a-a', children: Object.keys(component._children).length ? component._children : [], scripts: [], assets: [],
256
+ path: request.path ?? '/', method: request.method ?? 'GET', release: 'a-a-a', children: Object.keys(component._children).length ? component._children : [], scripts: Object.keys(component._effects.scripts ?? {}), assets: [],
242
257
  ...(request.lazy ? { lazyLoaded: false, lazyIsolated: true } : {}), ...(reactiveProps(component).length ? { props: reactiveProps(component) } : {}), locale: this.options.locale ?? 'en', islands: [],
243
258
  });
244
259
  const { html: _h, ...effects } = component._effects;
@@ -285,12 +300,13 @@ export class WireRuntime {
285
300
  component[prop] = passed[prop];
286
301
  await component.boot();
287
302
  await component.hydrate();
303
+ const locked = lockedProperties(component);
288
304
  const applyUpdate = async (path, value) => {
289
305
  const segments = path.split('.');
290
306
  const prop = segments[0];
291
307
  if (!known.has(prop))
292
308
  throw new WireError(404, `Public property not found: ${prop}`);
293
- if (cls.locked?.includes(prop))
309
+ if (locked.has(prop))
294
310
  throw new WireError(403, `Locked property: ${prop}`);
295
311
  if (reactive.has(prop))
296
312
  throw new WireError(403, `Reactive property, set by the parent component: ${prop}`);
@@ -395,6 +411,17 @@ export class WireRuntime {
395
411
  memo.lazyLoaded = true;
396
412
  delete memo.lazyIsolated;
397
413
  }
414
+ // Scripts already run by the browser are not sent again.
415
+ if (effects.scripts) {
416
+ const known = new Set(Array.isArray(memo.scripts) ? memo.scripts : []);
417
+ const fresh = Object.fromEntries(Object.entries(effects.scripts).filter(([key]) => !known.has(key)));
418
+ if (Object.keys(fresh).length) {
419
+ effects.scripts = fresh;
420
+ memo.scripts = [...known, ...Object.keys(fresh)];
421
+ }
422
+ else
423
+ delete effects.scripts;
424
+ }
398
425
  return { snapshot: this.snapshot(component, name, memo), effects };
399
426
  }
400
427
  /** HTTP entry point; body: `{ _token, components: [{ snapshot, updates, calls }] }`. */
package/lib/uploads.js CHANGED
@@ -135,6 +135,19 @@ async function detect(file, filename) {
135
135
  const { buffer, bytesRead } = await handle.read(Buffer.alloc(512), 0, 512, 0);
136
136
  const head = buffer.subarray(0, bytesRead);
137
137
  const known = SIGNATURES.find((signature) => signature.test(head));
138
+ if (known?.extension === 'zip') {
139
+ // Office documents are ZIP archives: their central directory (at the end) names their main part.
140
+ const { size } = await handle.stat();
141
+ const length = Math.min(size, 66 * 1024);
142
+ const { buffer: tail } = await handle.read(Buffer.alloc(length), 0, length, size - length);
143
+ const names = tail.toString('latin1');
144
+ if (names.includes('xl/workbook.xml'))
145
+ return { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', extension: 'xlsx' };
146
+ if (names.includes('word/document.xml'))
147
+ return { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', extension: 'docx' };
148
+ if (names.includes('ppt/presentation.xml'))
149
+ return { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', extension: 'pptx' };
150
+ }
138
151
  if (known)
139
152
  return { type: known.type, extension: known.extension };
140
153
  const extension = path.extname(String(filename ?? '')).slice(1).toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navette-js/wire",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Server-side runtime compatible with Livewire 3 for Node.js and Express.",
5
5
  "license": "MIT",
6
6
  "type": "module",