lecodes-cli 2.2.13 → 2.2.14

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/index.js CHANGED
@@ -64,7 +64,7 @@ var package_default;
64
64
  var init_package = __esm(() => {
65
65
  package_default = {
66
66
  name: "lecodes-cli",
67
- version: "2.2.13",
67
+ version: "2.2.14",
68
68
  dependencies: {
69
69
  "@letary/chisel": "^0.15.0",
70
70
  jimp: "^1.6.1",
@@ -88,7 +88,7 @@ var init_package = __esm(() => {
88
88
  },
89
89
  peerDependencies: {
90
90
  "lecodes-design": "^0.11.0",
91
- "lecodes-headless": "^2.2.13",
91
+ "lecodes-headless": "^2.2.14",
92
92
  "lecodes-3d-editor": "^0.9.0",
93
93
  "lecodes-assets": "^0.2.1",
94
94
  "heic-decode": "^2.1.0",
@@ -158,7 +158,7 @@ var init_package = __esm(() => {
158
158
  });
159
159
 
160
160
  // ../../sdk/src/version.ts
161
- var SDK_VERSION = "2.2.13";
161
+ var SDK_VERSION = "2.2.14";
162
162
 
163
163
  // src/cli/args.ts
164
164
  var isNegativeNumber = (s) => /^-(\d|\.\d)/.test(s), isValue = (s) => !s.startsWith("-") || isNegativeNumber(s), parseArgv = (argv, booleans = new Set) => {
@@ -170307,7 +170307,7 @@ var normalizePath2 = (path) => {
170307
170307
  continue;
170308
170308
  const exported = new Map(checker.getExportsOfModule(modSym).map((s) => [s.name, s]));
170309
170309
  for (const ex of exports) {
170310
- if (ex.kind === "other")
170310
+ if (ex.kind === "other" || ex.kind === "push")
170311
170311
  continue;
170312
170312
  const id = `${serverModuleId(path)}#${ex.name}`;
170313
170313
  let sym = exported.get(ex.name);
@@ -170628,6 +170628,8 @@ var SERVER_FILE, isServerPath = (path) => SERVER_FILE.test(path), serverModuleId
170628
170628
  const s = init.trimStart();
170629
170629
  if (/^channel\s*[<(]/.test(s))
170630
170630
  return "channel";
170631
+ if (/^pushChannel\s*[<(]/.test(s))
170632
+ return "push";
170631
170633
  if (/^async\s+function\b/.test(s))
170632
170634
  return "fn";
170633
170635
  if (/^async\b/.test(s))
@@ -170671,7 +170673,7 @@ var SERVER_FILE, isServerPath = (path) => SERVER_FILE.test(path), serverModuleId
170671
170673
  }
170672
170674
  }
170673
170675
  return found.sort((a, b) => a.at - b.at).map(({ name, kind }) => ({ name, kind }));
170674
- }, serverStubSource = (path, exports, serverUrl) => {
170676
+ }, serverStubSource = (path, exports, serverUrl, push = exports.some((e) => e.kind === "push")) => {
170675
170677
  const mod = serverModuleId(path);
170676
170678
  const url = JSON.stringify(serverUrl);
170677
170679
  const lines = exports.map(({ name, kind }) => {
@@ -170680,8 +170682,12 @@ var SERVER_FILE, isServerPath = (path) => SERVER_FILE.test(path), serverModuleId
170680
170682
  return `export const ${name} = __rpc(${url}, ${id})`;
170681
170683
  if (kind === "channel")
170682
170684
  return `export const ${name} = __channel(${url}, ${id})`;
170685
+ if (kind === "push")
170686
+ return `export const ${name} = __pushChannel(${url}, ${id})`;
170683
170687
  return `export const ${name} = __serverOnly(${id})`;
170684
170688
  });
170689
+ if (push)
170690
+ lines.unshift("__pushEnable()");
170685
170691
  return `// client stub of ${mod} — generated by the lecodes compiler
170686
170692
  ${lines.join(`
170687
170693
  `)}
@@ -170689,9 +170695,9 @@ ${lines.join(`
170689
170695
  }, ClientPublishError, clientPublishes = (entries) => {
170690
170696
  const channels = new Map;
170691
170697
  for (const [path, exports] of Object.entries(scanServerModules(entries))) {
170692
- const names = exports.filter((e) => e.kind === "channel").map((e) => e.name);
170693
- if (names.length)
170694
- channels.set(path.replace(/\.[tj]s$/, ""), new Set(names));
170698
+ const names = new Map(exports.filter((e) => e.kind === "channel" || e.kind === "push").map((e) => [e.name, e.kind === "push" ? "send" : "publish"]));
170699
+ if (names.size)
170700
+ channels.set(path.replace(/\.[tj]s$/, ""), names);
170695
170701
  }
170696
170702
  if (channels.size === 0)
170697
170703
  return [];
@@ -170710,17 +170716,18 @@ ${lines.join(`
170710
170716
  continue;
170711
170717
  for (const part of m[1].split(",")) {
170712
170718
  const [imported, local] = part.split(/\s+as\s+/).map((s) => s.trim());
170713
- if (imported && names.has(imported))
170714
- locals.push(local ?? imported);
170719
+ const verb = imported ? names.get(imported) : undefined;
170720
+ if (verb)
170721
+ locals.push([local ?? imported, verb]);
170715
170722
  }
170716
170723
  }
170717
170724
  if (!locals.length)
170718
170725
  continue;
170719
170726
  const code = blankCommentsAndStrings(src);
170720
- for (const local of locals) {
170721
- for (const m of code.matchAll(new RegExp(`(?<![\\w$.])${local.replace(/\$/g, "\\$")}\\s*\\.\\s*publish\\s*\\(`, "g"))) {
170727
+ for (const [local, verb] of locals) {
170728
+ for (const m of code.matchAll(new RegExp(`(?<![\\w$.])${local.replace(/\$/g, "\\$")}\\s*\\.\\s*${verb}\\s*\\(`, "g"))) {
170722
170729
  out.push(`${serverModuleId(e.path)}:${code.slice(0, m.index).split(`
170723
- `).length}: ${local}.publish(…)`);
170730
+ `).length}: ${local}.${verb}(…)`);
170724
170731
  }
170725
170732
  }
170726
170733
  }
@@ -170760,7 +170767,11 @@ ${lines.join(`
170760
170767
  out[normalizePath3(e.path)] = scanServerExports(e.text ?? "", serverModuleId(e.path));
170761
170768
  }
170762
170769
  return out;
170763
- }, stubServerEntries = (entries, serverUrl) => entries.map((e) => e.type === "text" && isServerPath(e.path) ? { ...e, text: serverStubSource(e.path, scanServerExports(e.text ?? "", serverModuleId(e.path)), serverUrl) } : e), compileServerBundle = async (opts) => {
170770
+ }, stubServerEntries = (entries, serverUrl) => {
170771
+ const modules = scanServerModules(entries);
170772
+ const push = Object.values(modules).some((exports) => exports.some((e) => e.kind === "push"));
170773
+ return entries.map((e) => e.type === "text" && isServerPath(e.path) ? { ...e, text: serverStubSource(e.path, modules[normalizePath3(e.path)] ?? [], serverUrl, push) } : e);
170774
+ }, compileServerBundle = async (opts) => {
170764
170775
  const modules = scanServerModules(opts.entries);
170765
170776
  if (Object.keys(modules).length === 0)
170766
170777
  return null;
@@ -170797,7 +170808,7 @@ var init_serverSplit = __esm(() => {
170797
170808
  ClientPublishError = class ClientPublishError extends Error {
170798
170809
  problems;
170799
170810
  constructor(problems) {
170800
- super(`a channel goes from the server to the app — the app subscribes; to send, call a server function that publishes:
170811
+ super(`a channel goes from the server to the app — the app listens; to send, call a server function that publishes (a push channel: that sends):
170801
170812
  ${problems.join(`
170802
170813
  `)}`);
170803
170814
  this.name = "ClientPublishError";
@@ -207697,15 +207708,26 @@ var init_channel = __esm(() => {
207697
207708
  seam = globalThis;
207698
207709
  });
207699
207710
 
207711
+ // ../../sdk/src/server/pushChannel.ts
207712
+ var seam2, setPushSender = (sender) => {
207713
+ if (sender)
207714
+ seam2.__lecodesPushSend = sender;
207715
+ else
207716
+ delete seam2.__lecodesPushSend;
207717
+ }, isPushChannel = (v) => typeof v === "object" && v !== null && v.__pushChannel === true;
207718
+ var init_pushChannel = __esm(() => {
207719
+ seam2 = globalThis;
207720
+ });
207721
+
207700
207722
  // ../../sdk/src/server/context.ts
207701
- var seam2, setRequestProvider = (provider) => {
207723
+ var seam3, setRequestProvider = (provider) => {
207702
207724
  if (provider)
207703
- seam2.__lecodesRequest = provider;
207725
+ seam3.__lecodesRequest = provider;
207704
207726
  else
207705
- delete seam2.__lecodesRequest;
207706
- }, currentRequest = () => seam2.__lecodesRequest?.();
207727
+ delete seam3.__lecodesRequest;
207728
+ }, currentRequest = () => seam3.__lecodesRequest?.();
207707
207729
  var init_context2 = __esm(() => {
207708
- seam2 = globalThis;
207730
+ seam3 = globalThis;
207709
207731
  });
207710
207732
 
207711
207733
  // ../../sdk/src/server/db/fields.ts
@@ -207793,30 +207815,30 @@ var FILE_MODEL = "File", filePublicFields = () => ({
207793
207815
  width: t.int().optional(),
207794
207816
  height: t.int().optional(),
207795
207817
  createdAt: t.date().default("now")
207796
- }), seam3, setFilesSeam = (value) => {
207818
+ }), seam4, setFilesSeam = (value) => {
207797
207819
  if (value)
207798
- seam3.__lecodesFiles = value;
207820
+ seam4.__lecodesFiles = value;
207799
207821
  else
207800
- delete seam3.__lecodesFiles;
207822
+ delete seam4.__lecodesFiles;
207801
207823
  };
207802
207824
  var init_models2 = __esm(() => {
207803
207825
  init_fields();
207804
- seam3 = globalThis;
207826
+ seam4 = globalThis;
207805
207827
  });
207806
207828
 
207807
207829
  // ../../sdk/src/server/db/defineDb.ts
207808
- var seam4, setDbTransport = (transport) => {
207830
+ var seam5, setDbTransport = (transport) => {
207809
207831
  if (transport)
207810
- seam4.__lecodesDbTransport = transport;
207832
+ seam5.__lecodesDbTransport = transport;
207811
207833
  else
207812
- delete seam4.__lecodesDbTransport;
207834
+ delete seam5.__lecodesDbTransport;
207813
207835
  }, takeRegisteredDbs = () => {
207814
- const dbs = seam4.__lecodesDbs ?? [];
207815
- seam4.__lecodesDbs = [];
207836
+ const dbs = seam5.__lecodesDbs ?? [];
207837
+ seam5.__lecodesDbs = [];
207816
207838
  return dbs;
207817
207839
  };
207818
207840
  var init_defineDb = __esm(() => {
207819
- seam4 = globalThis;
207841
+ seam5 = globalThis;
207820
207842
  });
207821
207843
 
207822
207844
  // ../../sdk/src/server/validate.ts
@@ -207931,7 +207953,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
207931
207953
  var isDb = (v) => typeof v === "object" && v !== null && typeof v.$schema === "string", loadServerModules = (reg = globalThis.__lecodesServer) => {
207932
207954
  if (!reg)
207933
207955
  throw new Error("loadServerModules: no server bundle registered (globalThis.__lecodesServer)");
207934
- const out = { endpoints: new Map, channels: new Map, dbs: [], manifest: reg.manifest };
207956
+ const out = { endpoints: new Map, channels: new Map, pushChannels: new Map, dbs: [], manifest: reg.manifest };
207935
207957
  for (const [mod, exports] of Object.entries(reg.modules)) {
207936
207958
  const kinds = new Map((reg.manifest?.modules?.[mod] ?? []).map((e) => [e.name, e.kind]));
207937
207959
  for (const [name, value] of Object.entries(exports)) {
@@ -207942,6 +207964,9 @@ var isDb = (v) => typeof v === "object" && v !== null && typeof v.$schema === "s
207942
207964
  } else if (isChannel(value)) {
207943
207965
  value.__id = id;
207944
207966
  out.channels.set(id, value);
207967
+ } else if (isPushChannel(value)) {
207968
+ value.__id = id;
207969
+ out.pushChannels.set(id, value);
207945
207970
  } else if (isDb(value))
207946
207971
  out.dbs.push(value);
207947
207972
  }
@@ -207961,9 +207986,10 @@ var isDb = (v) => typeof v === "object" && v !== null && typeof v.$schema === "s
207961
207986
  if (name !== "MarciEmbeddedError" && name !== "MarciHttpError" || typeof kind !== "string")
207962
207987
  return null;
207963
207988
  return DB_ERROR_STATUS[kind] ?? null;
207964
- }, als, installProvider = () => setRequestProvider(() => als.getStore()), withSession = (r, rc) => {
207965
- const issued = rc.auth?.issuedToken;
207966
- return issued ? { ...r, session: issued } : r;
207989
+ }, als, installProvider = () => setRequestProvider(() => als.getStore()), envelope = (server2) => (r, rc) => {
207990
+ const s = rc.auth;
207991
+ const out = s?.issuedToken ? { ...r, session: s.issuedToken } : r;
207992
+ return s && s.userId !== null && !s.pushAddress && server2.pushChannels.size ? { ...out, push: true } : out;
207967
207993
  }, createInvoker = (server2, opts = {}) => {
207968
207994
  installProvider();
207969
207995
  const validate = opts.validate ?? true;
@@ -207971,6 +207997,7 @@ var isDb = (v) => typeof v === "object" && v !== null && typeof v.$schema === "s
207971
207997
  const params = server2.manifest.params;
207972
207998
  const auth = opts.auth ?? null;
207973
207999
  const files = opts.files ?? null;
208000
+ const withSession = envelope(server2);
207974
208001
  return async (id, args, ctx = { headers: {} }) => {
207975
208002
  const fn = server2.endpoints.get(id);
207976
208003
  if (!fn)
@@ -208037,6 +208064,7 @@ var isDb = (v) => typeof v === "object" && v !== null && typeof v.$schema === "s
208037
208064
  const onError = opts.onError ?? ((id, e) => console.error(`[server] ${id}:`, e));
208038
208065
  const params = server2.manifest.params;
208039
208066
  const auth = opts.auth ?? null;
208067
+ const withSession = envelope(server2);
208040
208068
  return async (channelId, args = [], ctx = { headers: {} }) => {
208041
208069
  const ch = server2.channels.get(channelId);
208042
208070
  if (!ch)
@@ -208069,6 +208097,7 @@ var isDb = (v) => typeof v === "object" && v !== null && typeof v.$schema === "s
208069
208097
  };
208070
208098
  var init_runtime = __esm(() => {
208071
208099
  init_channel();
208100
+ init_pushChannel();
208072
208101
  init_context2();
208073
208102
  init_defineDb();
208074
208103
  init_validate();
@@ -208231,7 +208260,12 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
208231
208260
  if (!LOGIN_RE.test(l))
208232
208261
  throw new ApiError2(400, "Login must be 3–32 letters, digits, dots, dashes or underscores");
208233
208262
  return { provider: "login", subject: l.toLowerCase() };
208234
- }, identityKey = (provider, subject) => `${provider}:${subject}`, window2 = (map, key, now, span) => {
208263
+ }, identityKey = (provider, subject) => `${provider}:${subject}`, PUSH_ADDRESS_HEADER = "x-lecodes-push", PUSH_ADDRESS_RE, pushAddressOf = (ctx) => {
208264
+ const headers = ctx.headers ?? {};
208265
+ const raw = headers[PUSH_ADDRESS_HEADER] ?? Object.entries(headers).find(([k]) => k.toLowerCase() === PUSH_ADDRESS_HEADER)?.[1];
208266
+ const value = typeof raw === "string" ? raw.trim() : "";
208267
+ return PUSH_ADDRESS_RE.test(value) ? value : null;
208268
+ }, window2 = (map, key, now, span) => {
208235
208269
  const list = (map.get(key) ?? []).filter((x) => now - x < span);
208236
208270
  map.set(key, list);
208237
208271
  return list;
@@ -208262,9 +208296,10 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
208262
208296
  };
208263
208297
  const mint = async (ctx) => {
208264
208298
  const token = randomToken();
208265
- const { id } = await sessions.insert({ tokenHash: await sha2562(token) });
208266
- const state = { sessionId: id, userId: null, issuedToken: token };
208267
- sessionCache.set(token, { sessionId: id, userId: null, at: now() });
208299
+ const pushAddress = pushAddressOf(ctx);
208300
+ const { id } = await sessions.insert({ tokenHash: await sha2562(token), ...pushAddress ? { pushAddress } : {} });
208301
+ const state = { sessionId: id, userId: null, pushAddress, issuedToken: token };
208302
+ sessionCache.set(token, { sessionId: id, userId: null, pushAddress, at: now() });
208268
208303
  ctx.auth = state;
208269
208304
  ctx.sessionToken = token;
208270
208305
  return state;
@@ -208285,6 +208320,15 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
208285
208320
  if (opts.rotate)
208286
208321
  await rotate(ctx);
208287
208322
  };
208323
+ const followPushAddress = async (token, sessionId, held, told) => {
208324
+ if (!told || told === held)
208325
+ return held;
208326
+ await sessions.update(sessionId, { pushAddress: told });
208327
+ const c = sessionCache.get(token);
208328
+ if (c)
208329
+ c.pushAddress = told;
208330
+ return told;
208331
+ };
208288
208332
  const resolve = async (ctx) => {
208289
208333
  if (!tables)
208290
208334
  return;
@@ -208292,16 +208336,18 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
208292
208336
  if (token) {
208293
208337
  const c = sessionCache.get(token);
208294
208338
  if (c && now() - c.at < CACHE_TTL_MS) {
208295
- ctx.auth = { sessionId: c.sessionId, userId: c.userId };
208339
+ const pushAddress = await followPushAddress(token, c.sessionId, c.pushAddress, pushAddressOf(ctx));
208340
+ ctx.auth = { sessionId: c.sessionId, userId: c.userId, pushAddress };
208296
208341
  return;
208297
208342
  }
208298
- const row = await sessions.where({ tokenHash: await sha2562(token) }).select({ id: true, revoked: true, lastSeenAt: true, user: { id: true } }).first();
208343
+ const row = await sessions.where({ tokenHash: await sha2562(token) }).select({ id: true, revoked: true, lastSeenAt: true, pushAddress: true, user: { id: true } }).first();
208299
208344
  if (row && !row.revoked) {
208300
208345
  const userId = row.user ? row.user.id : null;
208301
208346
  if (now() - (row.lastSeenAt ?? 0) > SEEN_UPDATE_MS)
208302
208347
  sessions.update(row.id, { lastSeenAt: now() }).then(undefined, () => {});
208303
- sessionCache.set(token, { sessionId: row.id, userId, at: now() });
208304
- ctx.auth = { sessionId: row.id, userId };
208348
+ sessionCache.set(token, { sessionId: row.id, userId, pushAddress: row.pushAddress ?? null, at: now() });
208349
+ const pushAddress = await followPushAddress(token, row.id, row.pushAddress ?? null, pushAddressOf(ctx));
208350
+ ctx.auth = { sessionId: row.id, userId, pushAddress };
208305
208351
  return;
208306
208352
  }
208307
208353
  }
@@ -208493,6 +208539,92 @@ var init_host = __esm(() => {
208493
208539
  enc = new TextEncoder;
208494
208540
  EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
208495
208541
  LOGIN_RE = /^[\p{L}\p{N}_.-]{3,32}$/u;
208542
+ PUSH_ADDRESS_RE = /^[A-Za-z0-9_-]{1,128}$/;
208543
+ });
208544
+
208545
+ // ../../sdk/src/server/push/host.ts
208546
+ var PUSH_BATCH = 500, chunks = (list, size) => {
208547
+ const out = [];
208548
+ for (let i = 0;i < list.length; i += size)
208549
+ out.push(list.slice(i, i + size));
208550
+ return out;
208551
+ }, createPushHost = (cfg) => {
208552
+ const sessions = cfg.db?.$auth?.session ?? null;
208553
+ const relay = cfg.relay ?? null;
208554
+ const log = cfg.log ?? ((level, text) => level === "warn" ? console.warn(text) : console.info(text));
208555
+ const warned = new Set;
208556
+ const devices = async (userIds) => {
208557
+ const rows = await sessions.where({ user: { id: { $in: userIds } }, revoked: false }).select({ id: true, pushAddress: true });
208558
+ const byAddress = new Map;
208559
+ for (const row of rows) {
208560
+ if (!row.pushAddress)
208561
+ continue;
208562
+ const list = byAddress.get(row.pushAddress) ?? [];
208563
+ list.push(row.id);
208564
+ byAddress.set(row.pushAddress, list);
208565
+ }
208566
+ return byAddress;
208567
+ };
208568
+ const forget = async (byAddress, addresses) => {
208569
+ const ids = addresses.flatMap((a) => byAddress.get(a) ?? []);
208570
+ if (!ids.length)
208571
+ return;
208572
+ await sessions.where({ id: { $in: ids } }).updateMany({ pushAddress: null });
208573
+ };
208574
+ const send = async (channelId, userIds, message) => {
208575
+ if (!sessions) {
208576
+ throw new Error("pushChannel.send: the project's database has no sign-in — a user's devices are the sessions of defineDb({...}).withAuth({ model }), without it there is no one to send to");
208577
+ }
208578
+ if (!relay) {
208579
+ if (!warned.has(channelId)) {
208580
+ warned.add(channelId);
208581
+ log("warn", `${channelId}: notifications are not sent from this backend (no road to the platform's sender) — a deployed backend sends them`);
208582
+ }
208583
+ return { sent: 0 };
208584
+ }
208585
+ const byAddress = await devices(userIds);
208586
+ if (byAddress.size === 0)
208587
+ return { sent: 0 };
208588
+ const wire = {
208589
+ channel: channelId,
208590
+ title: message.title,
208591
+ ...message.body !== undefined ? { body: message.body } : {},
208592
+ ...message.url !== undefined ? { url: message.url } : {},
208593
+ ...message.data !== undefined ? { data: message.data } : {},
208594
+ ...message.badge !== undefined ? { badge: message.badge } : {}
208595
+ };
208596
+ let sent = 0;
208597
+ for (let batch of chunks([...byAddress.keys()], PUSH_BATCH)) {
208598
+ let result = await relay(batch, wire);
208599
+ if (result.unknown?.length) {
208600
+ const unknown = new Set(result.unknown);
208601
+ await forget(byAddress, [...unknown]);
208602
+ batch = batch.filter((a) => !unknown.has(a));
208603
+ if (!batch.length)
208604
+ continue;
208605
+ result = await relay(batch, wire);
208606
+ if (result.unknown?.length)
208607
+ await forget(byAddress, result.unknown);
208608
+ }
208609
+ sent += result.sent;
208610
+ const dead = result.failed.filter((f) => f.reason === "disabled").map((f) => f.address);
208611
+ if (dead.length)
208612
+ await forget(byAddress, dead);
208613
+ }
208614
+ return { sent };
208615
+ };
208616
+ return {
208617
+ get enabled() {
208618
+ return relay !== null;
208619
+ },
208620
+ install() {
208621
+ setPushSender(send);
208622
+ },
208623
+ send
208624
+ };
208625
+ };
208626
+ var init_host2 = __esm(() => {
208627
+ init_pushChannel();
208496
208628
  });
208497
208629
 
208498
208630
  // ../../sdk/src/server/files/host.ts
@@ -208891,7 +209023,7 @@ var UploadError, UUID, JOURNAL = "lecodes-files", MB, TMP_MAX_AGE_MS, MAX_IMAGE_
208891
209023
  }
208892
209024
  };
208893
209025
  };
208894
- var init_host2 = __esm(() => {
209026
+ var init_host3 = __esm(() => {
208895
209027
  init_context2();
208896
209028
  init_models2();
208897
209029
  UploadError = class UploadError extends Error {
@@ -208964,15 +209096,17 @@ var describeField = (f) => {
208964
209096
  models,
208965
209097
  endpoints: [...server2.endpoints.keys()].map((id) => ({ id, params: params[id] ?? null })),
208966
209098
  channels: [...server2.channels.keys()],
209099
+ pushChannels: [...server2.pushChannels.keys()],
208967
209100
  auth: { model: db?.$auth?.model ?? null },
208968
209101
  appName: server2.manifest.app?.name
208969
209102
  };
208970
209103
  };
208971
- var init_host3 = __esm(() => {
209104
+ var init_host4 = __esm(() => {
208972
209105
  init_runtime();
208973
209106
  init_defineDb();
208974
209107
  init_host();
208975
209108
  init_host2();
209109
+ init_host3();
208976
209110
  init_models2();
208977
209111
  init_models();
208978
209112
  });
@@ -210046,6 +210180,16 @@ var post = (m) => {
210046
210180
  await db.$sync(schema);
210047
210181
  return [`database started over — the schema change couldn't be migrated (${reason}); the previous data is in ${aside}`];
210048
210182
  }
210183
+ }, pushRelay = (sender) => async (to, message) => {
210184
+ try {
210185
+ return await sendPush(sender.apiUrl, sender.token, sender.uuid, { to, ...message });
210186
+ } catch (e) {
210187
+ const body = e instanceof ApiError ? e.body : null;
210188
+ if (body?.code === "unknown_addresses" && Array.isArray(body.addresses)) {
210189
+ return { sent: 0, failed: [], unknown: body.addresses.filter((a) => typeof a === "string") };
210190
+ }
210191
+ throw new Error(`push: the platform refused the send: ${e.message}`);
210192
+ }
210049
210193
  }, load = async (m) => {
210050
210194
  const noDb = () => {
210051
210195
  throw new Error("db: the database is not open yet — query it inside an endpoint, not while the module loads");
@@ -210064,6 +210208,11 @@ var post = (m) => {
210064
210208
  log: (level, text) => post({ t: "log", level, text })
210065
210209
  });
210066
210210
  authHost.install();
210211
+ createPushHost({
210212
+ db: server2.dbs.find((d) => d.$auth) ?? null,
210213
+ relay: m.push ? pushRelay(m.push) : null,
210214
+ log: (level, text) => post({ t: "log", level, text })
210215
+ }).install();
210067
210216
  const auth = authHost.enabled ? authHost : null;
210068
210217
  const onError = (id, e) => post({ t: "log", level: "error", text: `${id}: ${e?.stack ?? e}` });
210069
210218
  const files = createFilesHost({
@@ -210086,8 +210235,9 @@ var post = (m) => {
210086
210235
  post({ t: "loaded", describe, notes });
210087
210236
  };
210088
210237
  var init_backendWorker = __esm(() => {
210089
- init_host3();
210238
+ init_host4();
210090
210239
  init_peerInstall();
210240
+ init_api();
210091
210241
  console.log = hook("log");
210092
210242
  console.info = hook("info");
210093
210243
  console.warn = hook("warn");
@@ -217057,10 +217207,11 @@ init_cli();
217057
217207
  import { basename as basename14 } from "node:path";
217058
217208
 
217059
217209
  // src/dev/projectBackend.ts
217060
- init_host3();
217210
+ init_host4();
217061
217211
  init_projectCompile();
217062
217212
  init_peerInstall();
217063
217213
  init_manifest();
217214
+ init_shared();
217064
217215
  import { rmSync as rmSync9 } from "node:fs";
217065
217216
  import { join as join35, relative as relative5 } from "node:path";
217066
217217
 
@@ -217153,7 +217304,7 @@ var startLocalBackend = (options) => {
217153
217304
  log(`[server:error] the server process exited (${status ?? "killed"}) — it starts again with the next call`);
217154
217305
  }
217155
217306
  });
217156
- child.send({ t: "load", code: bundle, dbDir, filesDir, root: options.root });
217307
+ child.send({ t: "load", code: bundle, dbDir, filesDir, root: options.root, push: options.push ?? null });
217157
217308
  });
217158
217309
  const stop = async (l) => {
217159
217310
  if (live === l)
@@ -217514,7 +217665,15 @@ var createProjectBackend = (options) => {
217514
217665
  subscribe: (_scope, ch, args, ctx) => backend ? backend.subscribe(ch, args, ctx) : Promise.resolve({ ok: false, status: 404, message: "The project has no server files" })
217515
217666
  });
217516
217667
  const sockets = new Set;
217517
- const start = () => startLocalBackend({ root, dbDir, log, onPublish: (ch, group, data) => hub.publish("", ch, group, data) });
217668
+ const push = (() => {
217669
+ try {
217670
+ const { apiUrl, token } = session();
217671
+ return { apiUrl, token, uuid: readManifest(root).uuid };
217672
+ } catch {
217673
+ return null;
217674
+ }
217675
+ })();
217676
+ const start = () => startLocalBackend({ root, dbDir, log, push, onPublish: (ch, group, data) => hub.publish("", ch, group, data) });
217518
217677
  const envelope = (body) => new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json", "cache-control": "no-store", ...CORS } });
217519
217678
  return {
217520
217679
  compile: async (entries) => {
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "lecodes-cli",
3
- "version": "2.2.13",
3
+ "version": "2.2.14",
4
4
  "dependencies": {
5
5
  "@letary/chisel": "^0.15.0",
6
6
  "jimp": "^1.6.1",
7
- "lecodes-sdk": "2.2.13"
7
+ "lecodes-sdk": "2.2.14"
8
8
  },
9
9
  "devDependencies": {
10
10
  "lecodes-design": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-design/lecodes-design-0.11.0.tgz",
11
- "lecodes-headless": "2.2.13",
11
+ "lecodes-headless": "2.2.14",
12
12
  "qrcode-terminal": "^0.12.0",
13
13
  "lecodes-3d-editor": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-3d-editor/lecodes-3d-editor-0.9.0.tgz",
14
14
  "lecodes-assets": "0.2.1",
15
15
  "lecodes-creator-nav": "0.19.0",
16
- "lecodes-gl-lite": "2.2.13",
17
- "lecodes-web-frame": "2.2.13",
18
- "lecodes-web-player": "2.2.13",
19
- "lecodes-web-shared": "2.2.13",
16
+ "lecodes-gl-lite": "2.2.14",
17
+ "lecodes-web-frame": "2.2.14",
18
+ "lecodes-web-player": "2.2.14",
19
+ "lecodes-web-shared": "2.2.14",
20
20
  "lecodes-web-lite": "2.0.2",
21
21
  "heic-decode": "^2.1.0",
22
22
  "marcidb-embedded": "^0.15.0",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "peerDependencies": {
26
26
  "lecodes-design": "^0.11.0",
27
- "lecodes-headless": "^2.2.13",
27
+ "lecodes-headless": "^2.2.14",
28
28
  "lecodes-3d-editor": "^0.9.0",
29
29
  "lecodes-assets": "^0.2.1",
30
30
  "heic-decode": "^2.1.0",