gofront 1.2.3 → 1.3.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/src/pwa.js ADDED
@@ -0,0 +1,138 @@
1
+ // Offline PWA & Service Worker Generation for GoFront.
2
+ // Implements zero-configuration pre-caching and service worker emission.
3
+
4
+ import {
5
+ existsSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { join, resolve } from "node:path";
12
+
13
+ export const PWA_REGISTER_SNIPPET = `<script id="gofront-pwa">
14
+ if ('serviceWorker' in navigator) {
15
+ window.addEventListener('load', () => {
16
+ navigator.serviceWorker.register('./sw.js').catch(() => {});
17
+ });
18
+ }
19
+ </script>`;
20
+
21
+ export function collectPwaAssets(outDir) {
22
+ const assets = [];
23
+ function walk(dir, prefix = "") {
24
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
25
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
26
+ const full = join(dir, entry.name);
27
+ if (entry.isDirectory()) {
28
+ walk(full, rel);
29
+ } else if (entry.isFile()) {
30
+ if (entry.name !== "sw.js" && !entry.name.endsWith(".map")) {
31
+ assets.push(`./${rel}`);
32
+ }
33
+ }
34
+ }
35
+ }
36
+ if (existsSync(outDir)) {
37
+ walk(outDir);
38
+ }
39
+ if (assets.includes("./index.html") && !assets.includes("./")) {
40
+ assets.unshift("./");
41
+ }
42
+ return assets.sort();
43
+ }
44
+
45
+ export function generateServiceWorkerSource(assets, version) {
46
+ const assetList = JSON.stringify(assets, null, 2);
47
+ return `// GoFront PWA Service Worker — offline cache
48
+ const CACHE_NAME = "gofront-${version}";
49
+ const ASSETS = ${assetList};
50
+
51
+ self.addEventListener("install", (event) => {
52
+ event.waitUntil(
53
+ caches.open(CACHE_NAME).then((cache) => {
54
+ return cache.addAll(ASSETS);
55
+ }).then(() => self.skipWaiting()),
56
+ );
57
+ });
58
+
59
+ self.addEventListener("activate", (event) => {
60
+ event.waitUntil(
61
+ caches.keys().then((keys) => {
62
+ return Promise.all(
63
+ keys.map((key) => {
64
+ if (key !== CACHE_NAME && key.startsWith("gofront-")) {
65
+ return caches.delete(key);
66
+ }
67
+ }),
68
+ );
69
+ }).then(() => self.clients.claim()),
70
+ );
71
+ });
72
+
73
+ self.addEventListener("fetch", (event) => {
74
+ if (event.request.method !== "GET") return;
75
+ event.respondWith(
76
+ caches.match(event.request).then((cached) => {
77
+ if (cached) return cached;
78
+ return fetch(event.request).then((response) => {
79
+ if (!response || response.status !== 200 || response.type !== "basic") {
80
+ return response;
81
+ }
82
+ const clone = response.clone();
83
+ caches.open(CACHE_NAME).then((cache) => {
84
+ cache.put(event.request, clone);
85
+ });
86
+ return response;
87
+ }).catch(() => {
88
+ if (event.request.headers?.get?.("accept")?.includes("text/html")) {
89
+ return caches.match("./") || caches.match("./index.html");
90
+ }
91
+ });
92
+ }),
93
+ );
94
+ });
95
+ `;
96
+ }
97
+
98
+ export function injectPwaRegistration(html) {
99
+ if (
100
+ html.includes("gofront-pwa") ||
101
+ html.includes("navigator.serviceWorker.register")
102
+ ) {
103
+ return html;
104
+ }
105
+ if (/<\/body>/i.test(html)) {
106
+ return html.replace(/<\/body>/i, () => `${PWA_REGISTER_SNIPPET}\n</body>`);
107
+ }
108
+ return `${html}\n${PWA_REGISTER_SNIPPET}\n`;
109
+ }
110
+
111
+ export function generatePwa(outDir, options = {}) {
112
+ const resolvedOut = resolve(outDir);
113
+ mkdirSync(resolvedOut, { recursive: true });
114
+
115
+ const assets = collectPwaAssets(resolvedOut);
116
+ const version = options.version ?? `v${Date.now().toString(36)}`;
117
+ const swName = options.swName ?? "sw.js";
118
+ const swPath = join(resolvedOut, swName);
119
+
120
+ const swSource = generateServiceWorkerSource(assets, version);
121
+ writeFileSync(swPath, swSource);
122
+
123
+ const indexPath = join(resolvedOut, "index.html");
124
+ if (existsSync(indexPath)) {
125
+ const html = readFileSync(indexPath, "utf8");
126
+ const updatedHtml = injectPwaRegistration(html);
127
+ if (updatedHtml !== html) {
128
+ writeFileSync(indexPath, updatedHtml);
129
+ }
130
+ }
131
+
132
+ return {
133
+ swPath,
134
+ assetsCount: assets.length,
135
+ version,
136
+ assets,
137
+ };
138
+ }
@@ -7,7 +7,6 @@ import {
7
7
  CMP_OPS,
8
8
  COMPLEX128,
9
9
  FLOAT64,
10
- INT,
11
10
  isAny,
12
11
  isComplex,
13
12
  isComplexOrNumeric,
@@ -17,6 +16,8 @@ import {
17
16
  isString,
18
17
  isUntyped,
19
18
  LOG_OPS,
19
+ SIZED_FLOAT_NAMES,
20
+ SIZED_INT_NAMES,
20
21
  STRING,
21
22
  typeStr,
22
23
  UNTYPED_COMPLEX,
@@ -28,8 +29,18 @@ import {
28
29
 
29
30
  // Untyped constant assignability: maps source.base → Set of compatible target.name values
30
31
  const UNTYPED_COMPAT = {
31
- int: new Set(["int", "float64", "complex128", "complex64"]),
32
- float64: new Set(["float64", "int", "complex128", "complex64"]),
32
+ int: new Set([
33
+ ...SIZED_INT_NAMES,
34
+ ...SIZED_FLOAT_NAMES,
35
+ "complex128",
36
+ "complex64",
37
+ ]),
38
+ float64: new Set([
39
+ ...SIZED_FLOAT_NAMES,
40
+ ...SIZED_INT_NAMES,
41
+ "complex128",
42
+ "complex64",
43
+ ]),
33
44
  string: new Set(["string"]),
34
45
  bool: new Set(["bool"]),
35
46
  complex128: new Set(["complex128", "complex64"]),
@@ -92,16 +103,23 @@ export const assignabilityMethods = {
92
103
  },
93
104
 
94
105
  _binaryResultTypeNumeric(lt, rt) {
95
- const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
96
- const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
106
+ const lName = lt.kind === "untyped" ? lt.base : lt.name;
107
+ const rName = rt.kind === "untyped" ? rt.base : rt.name;
108
+ const lFloat = lName === "float32" || lName === "float64";
109
+ const rFloat = rName === "float32" || rName === "float64";
97
110
  const isFloat = lFloat || rFloat;
98
111
  if (lt.kind === "untyped" && rt.kind === "untyped")
99
112
  return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
100
113
  if (lt.kind === "untyped")
101
- return isFloat && rt.name !== "float64" ? FLOAT64 : rt;
114
+ return isFloat && rt.name !== "float64" && rt.name !== "float32"
115
+ ? FLOAT64
116
+ : rt;
102
117
  if (rt.kind === "untyped")
103
- return isFloat && lt.name !== "float64" ? FLOAT64 : lt;
104
- return isFloat ? FLOAT64 : INT;
118
+ return isFloat && lt.name !== "float64" && lt.name !== "float32"
119
+ ? FLOAT64
120
+ : lt;
121
+ if (lt.name === "float32" && rt.name === "float32") return lt;
122
+ return isFloat ? FLOAT64 : lt;
105
123
  },
106
124
 
107
125
  _checkComplexCmpOp(op, lt, rt, node) {
@@ -151,6 +169,7 @@ export const assignabilityMethods = {
151
169
  if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
152
170
  if (this._isNumericCoercible(target, source)) return;
153
171
  if (this._checkArrayAssignable(target, source, node)) return;
172
+ if (this._checkTypedArrayAssignable(target, source, node)) return;
154
173
  if (typeStr(target) !== typeStr(source))
155
174
  this._assertAssignableTypeMismatch(target, source, node);
156
175
  },
@@ -222,6 +241,40 @@ export const assignabilityMethods = {
222
241
  return false;
223
242
  },
224
243
 
244
+ _checkTypedArrayAssignable(target, source, node) {
245
+ const isTa = (t) => t?.underlying?._isTypedArray || t?._isTypedArray;
246
+ const getSlice = (t) =>
247
+ t?.kind === "slice"
248
+ ? t
249
+ : t?.underlying?.kind === "slice"
250
+ ? t.underlying
251
+ : null;
252
+ const targetTa = isTa(target);
253
+ const sourceTa = isTa(source);
254
+ const targetSlice = getSlice(target);
255
+ const sourceSlice = getSlice(source);
256
+
257
+ if ((targetTa && sourceSlice) || (targetSlice && sourceTa)) {
258
+ const taElem = targetTa
259
+ ? (target.underlying?._elemType ?? target._elemType)
260
+ : (source.underlying?._elemType ?? source._elemType);
261
+ const slElem = targetSlice ? targetSlice.elem : sourceSlice.elem;
262
+ if (typeStr(taElem) === typeStr(slElem)) return true;
263
+ this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
264
+ return true;
265
+ }
266
+
267
+ if (targetTa && sourceTa) {
268
+ const tElem = target.underlying?._elemType ?? target._elemType;
269
+ const sElem = source.underlying?._elemType ?? source._elemType;
270
+ if (typeStr(tElem) === typeStr(sElem)) return true;
271
+ this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
272
+ return true;
273
+ }
274
+
275
+ return false;
276
+ },
277
+
225
278
  // ── Interface satisfaction ────────────────────────────────────────
226
279
 
227
280
  _sigParamsMatch(reqList, actList) {
@@ -6,8 +6,10 @@ import {
6
6
  BASIC_TYPES,
7
7
  defaultType,
8
8
  isAny,
9
+ isTypedArraySlice,
9
10
  Scope,
10
11
  TAINTED_ANY,
12
+ typedArrayConstructorForElem,
11
13
  typeStr,
12
14
  VOID,
13
15
  } from "./types.js";
@@ -387,11 +389,17 @@ export const resolveMethods = {
387
389
  return this._fieldTypeInterface(base, baseType, field, node);
388
390
  case "namespace":
389
391
  return this._fieldTypeNamespace(base, field, node);
392
+ case "slice":
393
+ if (isTypedArraySlice(base)) {
394
+ return this._fieldTypeTypedArraySlice(base, baseType, field, node);
395
+ }
396
+ break;
390
397
  default:
391
- if (base && !isAny(base))
392
- return this._fieldTypeBadAccess(base, baseType, field, node);
393
- return ANY;
398
+ break;
394
399
  }
400
+ if (base && !isAny(base))
401
+ return this._fieldTypeBadAccess(base, baseType, field, node);
402
+ return ANY;
395
403
  },
396
404
 
397
405
  _fieldTypeStruct(base, baseType, field, node) {
@@ -402,9 +410,35 @@ export const resolveMethods = {
402
410
 
403
411
  _fieldTypeInterface(base, baseType, field, node) {
404
412
  if (base.methods?.has(field)) return base.methods.get(field);
413
+ if (base.fields?.has(field)) return base.fields.get(field);
414
+ if (base._isOpen) return ANY;
405
415
  return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
406
416
  },
407
417
 
418
+ _fieldTypeTypedArraySlice(base, baseType, field, node) {
419
+ const taName = typedArrayConstructorForElem(base.elem);
420
+ if (taName) {
421
+ const taType = this.types.get(taName);
422
+ if (taType) {
423
+ const underlying = taType.kind === "named" ? taType.underlying : taType;
424
+ if (underlying?.fields?.has(field)) return underlying.fields.get(field);
425
+ if (underlying?.methods?.has(field)) {
426
+ const m = underlying.methods.get(field);
427
+ if (field === "subarray" || field === "slice") {
428
+ return {
429
+ kind: "func",
430
+ params: m.params,
431
+ returns: [baseType],
432
+ variadic: m.variadic,
433
+ };
434
+ }
435
+ return m;
436
+ }
437
+ }
438
+ }
439
+ return this._fieldTypeBadAccess(base, baseType, field, node);
440
+ },
441
+
408
442
  _fieldTypeBadAccess(base, baseType, field, node) {
409
443
  if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
410
444
  const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
@@ -1,7 +1,16 @@
1
1
  // Core standard library type definitions — browser globals, fmt, strings, bytes,
2
2
  // strconv, sort, math, errors, time, unicode, os, slices, html, io.
3
3
 
4
- import { ANY, BOOL, ERROR, FLOAT64, INT, STRING, VOID } from "../types.js";
4
+ import {
5
+ ANY,
6
+ BOOL,
7
+ ERROR,
8
+ FLOAT64,
9
+ INT,
10
+ STRING,
11
+ UINT8,
12
+ VOID,
13
+ } from "../types.js";
5
14
 
6
15
  const TIME_T = { kind: "named", name: "time.Time", underlying: ANY };
7
16
 
@@ -64,14 +73,8 @@ export function setupCoreGlobals(globals, types) {
64
73
  "Blob",
65
74
  "File",
66
75
  "FileReader",
67
- "ArrayBuffer",
68
- "Uint8Array",
69
76
  "TextEncoder",
70
77
  "TextDecoder",
71
- "WebGLRenderingContext",
72
- "WebGL2RenderingContext",
73
- "GPUDevice",
74
- "GPUAdapter",
75
78
  ];
76
79
  for (const g of browserGlobals) globals.define(g, ANY);
77
80
 
@@ -220,7 +223,7 @@ export function setupCoreGlobals(globals, types) {
220
223
  },
221
224
  });
222
225
 
223
- const BYTE_SLICE = { kind: "slice", elem: INT };
226
+ const BYTE_SLICE = { kind: "slice", elem: UINT8 };
224
227
  const byFn1 = (ret) => ({
225
228
  kind: "func",
226
229
  params: [BYTE_SLICE],
@@ -320,13 +323,13 @@ export function setupCoreGlobals(globals, types) {
320
323
  Unquote: { kind: "func", params: [STRING], returns: [STRING, ERROR] },
321
324
  AppendInt: {
322
325
  kind: "func",
323
- params: [{ kind: "slice", elem: INT }, INT, INT],
324
- returns: [{ kind: "slice", elem: INT }],
326
+ params: [BYTE_SLICE, INT, INT],
327
+ returns: [BYTE_SLICE],
325
328
  },
326
329
  AppendFloat: {
327
330
  kind: "func",
328
- params: [{ kind: "slice", elem: INT }, FLOAT64, INT, INT, INT],
329
- returns: [{ kind: "slice", elem: INT }],
331
+ params: [BYTE_SLICE, FLOAT64, INT, INT, INT],
332
+ returns: [BYTE_SLICE],
330
333
  },
331
334
  },
332
335
  });
@@ -570,7 +573,7 @@ export function setupCoreGlobals(globals, types) {
570
573
  },
571
574
  });
572
575
 
573
- const BYTE_SLICE_IO = { kind: "slice", elem: INT };
576
+ const BYTE_SLICE_IO = { kind: "slice", elem: UINT8 };
574
577
  globals.define("io", {
575
578
  kind: "namespace",
576
579
  name: "io",
@@ -1,7 +1,16 @@
1
1
  // Extended standard library type definitions — gom, maps, regexp, rand, utf8,
2
2
  // path, strings.Builder / bytes.Buffer types, and built-in functions.
3
3
 
4
- import { ANY, BOOL, ERROR, FLOAT64, INT, STRING, VOID } from "../types.js";
4
+ import {
5
+ ANY,
6
+ BOOL,
7
+ ERROR,
8
+ FLOAT64,
9
+ INT,
10
+ STRING,
11
+ UINT8,
12
+ VOID,
13
+ } from "../types.js";
5
14
 
6
15
  export function setupExtendedGlobals(globals, types) {
7
16
  const GOM_NODE_T = {
@@ -209,7 +218,7 @@ export function setupExtendedGlobals(globals, types) {
209
218
  },
210
219
  });
211
220
 
212
- const BYTE_SLICE_T = { kind: "slice", elem: INT };
221
+ const BYTE_SLICE_T = { kind: "slice", elem: UINT8 };
213
222
  const _builderBase = [
214
223
  ["WriteString", { kind: "func", params: [STRING], returns: [INT, ERROR] }],
215
224
  ["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],