tina4-nodejs 3.13.103 → 3.13.105

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.
Files changed (33) hide show
  1. package/CLAUDE.md +16 -2
  2. package/package.json +1 -1
  3. package/packages/cli/dist/bin.js +747 -57
  4. package/packages/core/dist/index.js +750 -57
  5. package/packages/core/src/authGate.ts +6 -0
  6. package/packages/core/src/index.ts +2 -0
  7. package/packages/core/src/queue.ts +75 -18
  8. package/packages/core/src/queueBackends/liteBackend.ts +17 -1
  9. package/packages/core/src/queueBackends/mongoBackend.ts +80 -14
  10. package/packages/core/src/server.ts +5 -0
  11. package/packages/core/src/sso.ts +285 -0
  12. package/packages/orm/dist/index.js +755 -62
  13. package/packages/orm/src/adapters/postgres.ts +2 -0
  14. package/packages/orm/src/baseModel.ts +63 -10
  15. package/packages/orm/src/index.ts +2 -0
  16. package/packages/orm/src/migration.ts +14 -5
  17. package/packages/orm/src/point.ts +105 -0
  18. package/packages/orm/src/queryBuilder.ts +66 -5
  19. package/packages/orm/src/sqlTranslator.ts +63 -0
  20. package/packages/orm/src/types.ts +5 -1
  21. package/packages/swagger/dist/index.js +11 -1
  22. package/packages/swagger/src/generator.ts +11 -1
  23. package/types/core/src/index.d.ts +2 -0
  24. package/types/core/src/queue.d.ts +41 -4
  25. package/types/core/src/queueBackends/liteBackend.d.ts +7 -1
  26. package/types/core/src/queueBackends/mongoBackend.d.ts +7 -2
  27. package/types/core/src/sso.d.ts +55 -0
  28. package/types/orm/src/baseModel.d.ts +13 -5
  29. package/types/orm/src/index.d.ts +2 -0
  30. package/types/orm/src/point.d.ts +24 -0
  31. package/types/orm/src/queryBuilder.d.ts +10 -1
  32. package/types/orm/src/sqlTranslator.d.ts +13 -0
  33. package/types/orm/src/types.d.ts +5 -1
@@ -582,13 +582,172 @@ var init_databaseUrl = __esm({
582
582
  }
583
583
  });
584
584
 
585
+ // src/point.ts
586
+ function formatCoordinate(value) {
587
+ return Object.is(value, -0) ? "0" : Number(value.toPrecision(15)).toString();
588
+ }
589
+ var DEFAULT_SRID, SpatialNotSupportedError, Point;
590
+ var init_point = __esm({
591
+ "src/point.ts"() {
592
+ "use strict";
593
+ DEFAULT_SRID = 4326;
594
+ SpatialNotSupportedError = class extends Error {
595
+ constructor(message) {
596
+ super(message);
597
+ this.name = "SpatialNotSupportedError";
598
+ }
599
+ };
600
+ Point = class _Point {
601
+ lon;
602
+ lat;
603
+ srid;
604
+ constructor(lon, lat, srid = DEFAULT_SRID) {
605
+ if (typeof lon === "boolean" || typeof lat === "boolean" || typeof srid === "boolean") {
606
+ throw new TypeError("Point longitude, latitude and SRID must be numbers");
607
+ }
608
+ this.lon = Number(lon);
609
+ this.lat = Number(lat);
610
+ this.srid = Number(srid);
611
+ if (!Number.isFinite(this.lon) || !Number.isFinite(this.lat) || !Number.isInteger(this.srid)) {
612
+ throw new TypeError("Point longitude and latitude must be finite numbers and SRID must be an integer");
613
+ }
614
+ if (this.srid === DEFAULT_SRID) {
615
+ if (this.lon < -180 || this.lon > 180) throw new RangeError(`Point longitude ${this.lon} is outside -180..180; Tina4 uses longitude, latitude order`);
616
+ if (this.lat < -90 || this.lat > 90) throw new RangeError(`Point latitude ${this.lat} is outside -90..90; Tina4 uses longitude, latitude order`);
617
+ }
618
+ Object.freeze(this);
619
+ }
620
+ get wkt() {
621
+ return `POINT(${formatCoordinate(this.lon)} ${formatCoordinate(this.lat)})`;
622
+ }
623
+ get ewkt() {
624
+ return `SRID=${this.srid};${this.wkt}`;
625
+ }
626
+ get geojson() {
627
+ return { type: "Point", coordinates: [this.lon, this.lat] };
628
+ }
629
+ toJSON() {
630
+ return this.geojson;
631
+ }
632
+ toArray() {
633
+ return [this.lon, this.lat];
634
+ }
635
+ static parse(value, srid = DEFAULT_SRID) {
636
+ if (value instanceof _Point) return value;
637
+ if (Array.isArray(value)) {
638
+ if (value.length < 2) throw new TypeError("Point coordinate pair needs longitude and latitude");
639
+ return new _Point(value[0], value[1], srid);
640
+ }
641
+ if (value && typeof value === "object" && !(value instanceof Uint8Array)) {
642
+ return _Point.fromGeoJson(value, srid);
643
+ }
644
+ if (value instanceof Uint8Array) return _Point.fromWkb(value, srid);
645
+ if (typeof value === "string") {
646
+ const text = value.trim();
647
+ const match = /^(?:SRID\s*=\s*(\d+)\s*;\s*)?POINT\s*(?:Z|M|ZM)?\s*\(\s*([-+0-9.eE]+)\s+([-+0-9.eE]+)(?:\s+[-+0-9.eE]+)*\s*\)$/i.exec(text);
648
+ if (match) return new _Point(match[2], match[3], match[1] ? Number(match[1]) : srid);
649
+ if (text.length >= 42 && text.length % 2 === 0 && /^[0-9a-f]+$/i.test(text)) {
650
+ return _Point.fromWkb(Uint8Array.from(Buffer.from(text, "hex")), srid);
651
+ }
652
+ }
653
+ throw new TypeError("Point must be Point, [longitude, latitude], WKT/EWKT, GeoJSON or WKB/EWKB");
654
+ }
655
+ static geometryBinding(value, srid = DEFAULT_SRID) {
656
+ if (value instanceof _Point || Array.isArray(value)) return [_Point.parse(value, srid).ewkt, "ewkt"];
657
+ if (value && typeof value === "object") {
658
+ const candidate = value;
659
+ const geometry = String(candidate.type).toLowerCase() === "feature" ? candidate.geometry : candidate;
660
+ const allowed = /* @__PURE__ */ new Set(["point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection"]);
661
+ if (!geometry || !allowed.has(String(geometry.type).toLowerCase())) throw new TypeError("GeoJSON geometry has an unsupported type");
662
+ return [JSON.stringify(geometry), "geojson"];
663
+ }
664
+ if (typeof value === "string" && /^\s*(?:SRID\s*=\s*\d+\s*;\s*)?(?:POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)\b/i.test(value)) {
665
+ return [/^\s*SRID/i.test(value) ? value.trim() : `SRID=${srid};${value.trim()}`, "ewkt"];
666
+ }
667
+ throw new TypeError("Geometry must be Point, coordinate pair, WKT/EWKT or GeoJSON");
668
+ }
669
+ static fromGeoJson(data, srid) {
670
+ const geometry = String(data.type).toLowerCase() === "feature" ? data.geometry : data;
671
+ if (!geometry || String(geometry.type).toLowerCase() !== "point") throw new TypeError("Point GeoJSON type must be Point");
672
+ const coordinates = geometry.coordinates;
673
+ if (!Array.isArray(coordinates) || coordinates.length < 2) throw new TypeError("Point GeoJSON coordinates must be [longitude, latitude]");
674
+ return new _Point(coordinates[0], coordinates[1], srid);
675
+ }
676
+ static fromWkb(raw, srid) {
677
+ if (raw.byteLength < 21) throw new TypeError("Point WKB is too short");
678
+ const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
679
+ const little = raw[0] === 1;
680
+ const typeWord = view.getUint32(1, little);
681
+ let offset = 5;
682
+ if ((typeWord & 536870912) !== 0) {
683
+ srid = view.getUint32(5, little);
684
+ offset = 9;
685
+ }
686
+ const code = (typeWord & ~(536870912 | 1073741824 | 2147483648)) % 1e3;
687
+ if (code !== 1 || raw.byteLength < offset + 16) throw new TypeError("WKB geometry is not a Point");
688
+ return new _Point(view.getFloat64(offset, little), view.getFloat64(offset + 8, little), srid);
689
+ }
690
+ };
691
+ }
692
+ });
693
+
585
694
  // src/sqlTranslator.ts
586
695
  var SQLTranslator, QueryCache;
587
696
  var init_sqlTranslator = __esm({
588
697
  "src/sqlTranslator.ts"() {
589
698
  "use strict";
590
699
  init_databaseUrl();
700
+ init_point();
591
701
  SQLTranslator = class _SQLTranslator {
702
+ static SPATIAL_ENGINES = /* @__PURE__ */ new Set(["postgres", "postgresql"]);
703
+ static SPATIAL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
704
+ static requireSpatial(engine, feature) {
705
+ const name = String(engine || "unknown").toLowerCase();
706
+ if (!_SQLTranslator.SPATIAL_ENGINES.has(name)) {
707
+ throw new SpatialNotSupportedError(
708
+ `${feature} is not supported on the '${name}' database engine. Tina4 GIS support is PostGIS-first: use PostgreSQL with CREATE EXTENSION postgis. Tina4 will not replace a spatial query with an approximate coordinate query.`
709
+ );
710
+ }
711
+ return name;
712
+ }
713
+ static spatialIdentifier(name, what = "column") {
714
+ if (!_SQLTranslator.SPATIAL_IDENTIFIER.test(name)) throw new TypeError(`Spatial ${what} is not a valid SQL identifier: ${name}`);
715
+ return name;
716
+ }
717
+ static pointColumnType(engine, srid = DEFAULT_SRID) {
718
+ _SQLTranslator.requireSpatial(engine, "PointField");
719
+ return `geography(Point,${srid})`;
720
+ }
721
+ static spatialIndex(engine, table2, column2) {
722
+ _SQLTranslator.requireSpatial(engine, "spatial index creation");
723
+ table2 = _SQLTranslator.spatialIdentifier(table2, "table");
724
+ column2 = _SQLTranslator.spatialIdentifier(column2);
725
+ return `CREATE INDEX IF NOT EXISTS ${table2.replaceAll(".", "_")}_${column2}_gist ON ${table2} USING GIST (${column2})`;
726
+ }
727
+ static pointLiteral(engine, srid = DEFAULT_SRID) {
728
+ _SQLTranslator.requireSpatial(engine, "spatial predicates");
729
+ return `ST_SetSRID(ST_MakePoint(?, ?), ${srid})::geography`;
730
+ }
731
+ static withinDistance(engine, column2, srid = DEFAULT_SRID) {
732
+ return `ST_DWithin(${_SQLTranslator.spatialIdentifier(column2)}, ${_SQLTranslator.pointLiteral(engine, srid)}, ?)`;
733
+ }
734
+ static distance(engine, column2, srid = DEFAULT_SRID) {
735
+ return `ST_Distance(${_SQLTranslator.spatialIdentifier(column2)}, ${_SQLTranslator.pointLiteral(engine, srid)})`;
736
+ }
737
+ static distanceAs(engine, column2, alias, srid = DEFAULT_SRID) {
738
+ return `${_SQLTranslator.distance(engine, column2, srid)} AS ${_SQLTranslator.spatialIdentifier(alias, "result alias")}`;
739
+ }
740
+ static geometryLiteral(engine, form, srid = DEFAULT_SRID) {
741
+ _SQLTranslator.requireSpatial(engine, "spatial predicates");
742
+ return form === "ewkt" ? "ST_GeogFromText(?)" : `ST_SetSRID(ST_GeomFromGeoJSON(?), ${srid})::geography`;
743
+ }
744
+ static intersects(engine, column2, form = "ewkt", srid = DEFAULT_SRID) {
745
+ return `ST_Intersects(${_SQLTranslator.spatialIdentifier(column2)}, ${_SQLTranslator.geometryLiteral(engine, form, srid)})`;
746
+ }
747
+ static bbox(engine, column2, srid = DEFAULT_SRID) {
748
+ _SQLTranslator.requireSpatial(engine, "bbox");
749
+ return `ST_Intersects(${_SQLTranslator.spatialIdentifier(column2)}, ST_MakeEnvelope(?, ?, ?, ?, ${srid})::geography)`;
750
+ }
592
751
  /**
593
752
  * Convert LIMIT/OFFSET to Firebird ROWS...TO syntax.
594
753
  *
@@ -8773,6 +8932,12 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
8773
8932
  }
8774
8933
  }
8775
8934
  if (!resolvedToken) {
8935
+ const sso = req2.session?.get?.("_tina4_sso");
8936
+ const identity = sso?.identity;
8937
+ if (identity?.issuer && identity?.subject) {
8938
+ req2.user = identity;
8939
+ return false;
8940
+ }
8776
8941
  const sessionToken = req2.session?.get?.("token");
8777
8942
  if (sessionToken && validToken(sessionToken)) {
8778
8943
  resolvedToken = sessionToken;
@@ -17084,17 +17249,67 @@ var init_mongoBackend = __esm({
17084
17249
  process.stdout.write("__OK__");
17085
17250
  }
17086
17251
  else if (operation === "retry") {
17087
- // Explicit manual re-queue (always re-enqueues). data = JSON
17088
- // { id, delaySeconds }.
17252
+ // Explicit manual re-queue. Serves BOTH Queue.retry(id) (revive
17253
+ // a dead-letter job) AND job.retry() (manual re-queue of a live
17254
+ // reserved/pending job) so the Mongo backend matches
17255
+ // LiteBackend's dual behaviour.
17256
+ //
17257
+ // 1) DL revival (Queue.retry(id) after fail exhausted retries).
17258
+ // Pre-3.13.105 this branch was BROKEN: the search filter was
17259
+ // { queue: queueName, id, status: "failed" } -- three separate
17260
+ // reasons it could never match. dead_letter() inserts under
17261
+ // queueName + ".dead_letter" (not queueName), carries
17262
+ // status "dead" (not "failed"), and the original under
17263
+ // queueName was already acked to "completed" by the time the
17264
+ // DL was written. Now we look up in the DL namespace by id,
17265
+ // delete the DL doc first (so an interrupted retry never
17266
+ // leaves both a DL and a fresh pending doc), and upsert the
17267
+ // original back to pending -- re-hydrating if the original
17268
+ // was purged (housekeeping) so a retry always works.
17269
+ // 2) Live-doc manual re-queue (job.retry() on a job the caller
17270
+ // just popped and wants back in pending). The live-doc path
17271
+ // is preserved from before 3.13.105.
17272
+ //
17273
+ // Returns __OK__ when either path acted; __NOT_FOUND__ when
17274
+ // neither the DL nor the live doc existed, so Queue.retry(id)
17275
+ // can now report the pre-3.13.105 blanket-true as false for
17276
+ // unknown ids. data = JSON { id, delaySeconds }.
17089
17277
  const info = JSON.parse(data);
17278
+ const dlTopic = queueName + ".dead_letter";
17279
+ const now = new Date().toISOString();
17090
17280
  const avail = info.delaySeconds > 0
17091
17281
  ? new Date(Date.now() + info.delaySeconds * 1000).toISOString()
17092
- : new Date().toISOString();
17093
- await col.updateOne(
17094
- { queue: queueName, id: info.id },
17095
- { $set: { status: "pending", availableAt: avail, reservedAt: null }, $inc: { attempts: 1 } },
17096
- );
17097
- process.stdout.write("__OK__");
17282
+ : now;
17283
+ const dlDoc = await col.findOne({ queue: dlTopic, id: info.id });
17284
+ if (dlDoc !== null) {
17285
+ await col.deleteOne({ _id: dlDoc._id });
17286
+ const payload = dlDoc.payload ?? {};
17287
+ const priority = dlDoc.priority ?? 0;
17288
+ await col.updateOne(
17289
+ { queue: queueName, id: info.id },
17290
+ {
17291
+ $set: {
17292
+ status: "pending",
17293
+ availableAt: avail,
17294
+ reservedAt: null,
17295
+ error: null,
17296
+ payload,
17297
+ priority,
17298
+ id: info.id,
17299
+ createdAt: dlDoc.createdAt ?? now,
17300
+ },
17301
+ $inc: { attempts: 1 },
17302
+ },
17303
+ { upsert: true },
17304
+ );
17305
+ process.stdout.write("__OK__");
17306
+ } else {
17307
+ const result = await col.updateOne(
17308
+ { queue: queueName, id: info.id },
17309
+ { $set: { status: "pending", availableAt: avail, reservedAt: null }, $inc: { attempts: 1 } },
17310
+ );
17311
+ process.stdout.write(result.matchedCount > 0 ? "__OK__" : "__NOT_FOUND__");
17312
+ }
17098
17313
  }
17099
17314
  else if (operation === "deadLetters") {
17100
17315
  const docs = await col.find({ queue: queueName + ".dead_letter" }).toArray();
@@ -17139,10 +17354,20 @@ var init_mongoBackend = __esm({
17139
17354
  process.stdout.write(String(revived));
17140
17355
  }
17141
17356
  else if (operation === "purge") {
17142
- // Delete docs by status (default: all for the topic). data = JSON { status }.
17357
+ // Delete docs by status (default: every doc for the topic).
17358
+ // Pre-3.13.105 this filtered by { queue: queueName, status } for
17359
+ // EVERY status -- correct for pending/reserved/completed, wrong
17360
+ // for the dead-letter states (dead/failed/dead_letter) which
17361
+ // live under queueName + ".dead_letter" and carry status "dead".
17362
+ // A purge("dead") therefore deleted nothing and returned 0.
17363
+ // data = JSON { status }.
17143
17364
  const info = data ? JSON.parse(data) : {};
17144
- const filter = { queue: queueName };
17145
- if (info.status) filter.status = info.status;
17365
+ const isDead = info.status && ["dead", "failed", "dead_letter"].includes(info.status);
17366
+ const filter = isDead
17367
+ ? { queue: queueName + ".dead_letter" }
17368
+ : (info.status
17369
+ ? { queue: queueName, status: info.status }
17370
+ : { queue: queueName });
17146
17371
  const res = await col.deleteMany(filter);
17147
17372
  process.stdout.write(String(res.deletedCount || 0));
17148
17373
  }
@@ -17239,9 +17464,15 @@ var init_mongoBackend = __esm({
17239
17464
  fail(queue, id, error, maxRetries, retryBackoff = 0) {
17240
17465
  this.execSync("fail", queue, JSON.stringify({ id, error, maxRetries, retryBackoff }));
17241
17466
  }
17242
- /** Explicit manual re-queue (always re-enqueues regardless of the retry limit). */
17467
+ /**
17468
+ * Revive a specific dead-letter job by id. Returns true if the DL was found
17469
+ * and revived, false otherwise (parity with LiteBackend.retry(queue, id)
17470
+ * and Python's mongo_backend.retry_job()). Pre-3.13.105 this returned void
17471
+ * and Queue.retry(id) reported success for every call, even for unknown ids.
17472
+ */
17243
17473
  retry(queue, id, delaySeconds = 0) {
17244
- this.execSync("retry", queue, JSON.stringify({ id, delaySeconds }));
17474
+ const out = this.execSync("retry", queue, JSON.stringify({ id, delaySeconds }));
17475
+ return out.includes("__OK__");
17245
17476
  }
17246
17477
  /** Jobs that exceeded max retries (the `<queue>.dead_letter` collection topic). */
17247
17478
  deadLetters(queue, maxRetries) {
@@ -17910,10 +18141,20 @@ var init_liteBackend = __esm({
17910
18141
  * Explicit re-queue requested by the caller (job.retry()).
17911
18142
  *
17912
18143
  * Always re-enqueues regardless of the retry limit — manual override,
17913
- * distinct from the automatic failJob() path.
18144
+ * distinct from the automatic failJob() path. Cleans up BOTH the
18145
+ * reservation record AND any dead-letter file for this id, so a caller
18146
+ * that iterates deadLetters() and calls .retry() on each doesn't leave
18147
+ * the failed/ directory carrying duplicates (PY-12-05, 3.13.105).
18148
+ * Aligns with retry(queue, jobId) which had always unlinked the
18149
+ * dead-letter file -- two spellings of the same intent that previously
18150
+ * diverged.
17914
18151
  */
17915
18152
  retryJob(queue, job, delaySeconds) {
17916
18153
  this.clearReservation(queue, job.id);
18154
+ try {
18155
+ unlinkSync6(join17(this.ensureFailedDir(queue), `${job.id}.queue-data`));
18156
+ } catch {
18157
+ }
17917
18158
  job.attempts = (job.attempts || 0) + 1;
17918
18159
  job.error = void 0;
17919
18160
  this.requeue(queue, job, delaySeconds ?? 0, void 0);
@@ -18112,7 +18353,19 @@ var init_queue = __esm({
18112
18353
  }
18113
18354
  }
18114
18355
  /**
18115
- * Count jobs filtered by status. Defaults to "pending".
18356
+ * Count jobs by status. Defaults to "pending".
18357
+ *
18358
+ * ``"pending"`` counts jobs waiting to be popped -- INCLUDES retryable-
18359
+ * but-attempted ones, because they live in the pending queue under the
18360
+ * auto-retry lifecycle (see failed()).
18361
+ * ``"reserved"`` counts jobs a consumer has popped but not yet
18362
+ * completed/failed (in-flight against the visibility timeout).
18363
+ * ``"completed"`` counts jobs the consumer has finished successfully.
18364
+ * ``"failed"``, ``"dead"``, ``"dead_letter"`` are ALIASES that all count
18365
+ * the dead-letter store -- jobs whose attempts >= maxRetries and that
18366
+ * have given up. Use deadLetters() to list them. Retryable-but-attempted
18367
+ * jobs are NOT counted by size("failed"); use failed() to list them or
18368
+ * size("pending") to include them in a total.
18116
18369
  */
18117
18370
  size(status2 = "pending") {
18118
18371
  const q = this.topic;
@@ -18162,13 +18415,17 @@ var init_queue = __esm({
18162
18415
  /**
18163
18416
  * Get jobs that failed at least once but are still being retried
18164
18417
  * (0 < attempts < maxRetries). These live in the pending queue under the
18165
- * auto-retry lifecycle; dead-lettered jobs are returned by deadLetters().
18418
+ * auto-retry lifecycle (fail() re-queues them with an incremented attempts
18419
+ * count and a retryBackoff delay) so pop() picks them up again. They are
18420
+ * NOT counted by size("failed") -- that alias counts the dead-letter store,
18421
+ * matching deadLetters(). To include retryable-failed jobs in a total, use
18422
+ * size("pending"). Terminal failures are returned by deadLetters().
18166
18423
  */
18167
18424
  failed() {
18168
- if (this.externalBackend?.failed) {
18169
- return this.externalBackend.failed(this.topic, this._maxRetries);
18170
- }
18171
- return this.liteBackend.failed(this.topic, this._maxRetries);
18425
+ const raw = this.externalBackend?.failed ? this.externalBackend.failed(this.topic, this._maxRetries) : this.liteBackend.failed(this.topic, this._maxRetries);
18426
+ return raw.map(
18427
+ (data) => createJob({ ...data, topic: data.topic ?? this.topic }, this)
18428
+ );
18172
18429
  }
18173
18430
  /**
18174
18431
  * Retry all dead letter jobs for this queue's topic.
@@ -18180,8 +18437,8 @@ var init_queue = __esm({
18180
18437
  retry(jobId, delaySeconds) {
18181
18438
  if (jobId) {
18182
18439
  if (this.externalBackend?.retry) {
18183
- this.externalBackend.retry(this.topic, jobId, delaySeconds);
18184
- return true;
18440
+ const result = this.externalBackend.retry(this.topic, jobId, delaySeconds);
18441
+ return result === void 0 ? true : Boolean(result);
18185
18442
  }
18186
18443
  return this.liteBackend.retry(this.topic, jobId, delaySeconds);
18187
18444
  }
@@ -18190,8 +18447,8 @@ var init_queue = __esm({
18190
18447
  let retried = false;
18191
18448
  for (const job of deadJobs) {
18192
18449
  if (this.externalBackend?.retry) {
18193
- this.externalBackend.retry(this.topic, job.id, delaySeconds);
18194
- retried = true;
18450
+ const result = this.externalBackend.retry(this.topic, job.id, delaySeconds);
18451
+ if (result === void 0 || Boolean(result)) retried = true;
18195
18452
  } else if (this.liteBackend.retry(this.topic, job.id, delaySeconds)) {
18196
18453
  retried = true;
18197
18454
  }
@@ -18199,13 +18456,28 @@ var init_queue = __esm({
18199
18456
  return retried;
18200
18457
  }
18201
18458
  /**
18202
- * Get dead letter jobs failed jobs that exceeded max retries.
18459
+ * Get jobs that exceeded max_retries -- terminal failures.
18460
+ *
18461
+ * Same set counted by size("failed") / size("dead") / size("dead_letter")
18462
+ * (three aliases for the dead-letter store). To LIST retryable-but-
18463
+ * attempted jobs (attempts > 0 AND attempts < maxRetries) that are still
18464
+ * being auto-retried, use failed() -- those live in the pending queue and
18465
+ * are NOT dead letters.
18466
+ *
18467
+ * Returns Job objects with the failure reason on ``.error`` (not raw dicts)
18468
+ * so callers can iterate uniformly with the rest of the queue API and, in
18469
+ * particular, call ``.retry()`` on each to manually revive it:
18470
+ *
18471
+ * for (const job of queue.deadLetters()) {
18472
+ * Log.warn(`revived ${job.id}: ${job.error}`);
18473
+ * job.retry();
18474
+ * }
18203
18475
  */
18204
18476
  deadLetters(maxRetries) {
18205
- if (this.externalBackend?.deadLetters) {
18206
- return this.externalBackend.deadLetters(this.topic, maxRetries ?? this._maxRetries);
18207
- }
18208
- return this.liteBackend.deadLetters(this.topic, maxRetries ?? this._maxRetries);
18477
+ const raw = this.externalBackend?.deadLetters ? this.externalBackend.deadLetters(this.topic, maxRetries ?? this._maxRetries) : this.liteBackend.deadLetters(this.topic, maxRetries ?? this._maxRetries);
18478
+ return raw.map(
18479
+ (data) => createJob({ ...data, topic: data.topic ?? this.topic }, this)
18480
+ );
18209
18481
  }
18210
18482
  /**
18211
18483
  * Delete messages by status (e.g. "completed", "failed", "dead").
@@ -23324,6 +23596,14 @@ function resolveSecuritySchemes() {
23324
23596
  const apiKeyIn = ["header", "query", "cookie"].includes(rawIn) ? rawIn : "header";
23325
23597
  schemes.apiKeyAuth = { type: "apiKey", name: apiKeyName, in: apiKeyIn };
23326
23598
  }
23599
+ const ssoIssuer = (process.env.TINA4_SSO_ISSUER ?? "").replace(/\/$/, "");
23600
+ if (ssoIssuer) {
23601
+ schemes.oidc = {
23602
+ type: "openIdConnect",
23603
+ openIdConnectUrl: `${ssoIssuer}/.well-known/openid-configuration`
23604
+ };
23605
+ schemes.ssoSession = { type: "apiKey", in: "cookie", name: "tina4_session" };
23606
+ }
23327
23607
  for (const [name, def] of Object.entries(registeredSchemes)) {
23328
23608
  schemes[name] = def;
23329
23609
  }
@@ -23505,7 +23785,9 @@ function generate(routes, models = []) {
23505
23785
  if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
23506
23786
  }
23507
23787
  } else if (routeRequiresAuth(route, method)) {
23508
- operation.security = sanitizeSecurity([{ [defaultScheme]: [] }], schemes);
23788
+ const requirements = [{ [defaultScheme]: [] }];
23789
+ if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
23790
+ operation.security = sanitizeSecurity(requirements, schemes);
23509
23791
  const responses = operation.responses;
23510
23792
  if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
23511
23793
  }
@@ -23819,6 +24101,298 @@ var init_src = __esm({
23819
24101
  }
23820
24102
  });
23821
24103
 
24104
+ // ../core/src/sso.ts
24105
+ var sso_exports = {};
24106
+ __export(sso_exports, {
24107
+ SSO: () => Sso,
24108
+ Sso: () => Sso,
24109
+ SsoError: () => SsoError
24110
+ });
24111
+ import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
24112
+ var SsoError, Sso;
24113
+ var init_sso = __esm({
24114
+ "../core/src/sso.ts"() {
24115
+ "use strict";
24116
+ SsoError = class extends Error {
24117
+ };
24118
+ Sso = class _Sso {
24119
+ static PENDING_KEY = "_tina4_sso_pending";
24120
+ static SESSION_KEY = "_tina4_sso";
24121
+ issuer;
24122
+ clientId;
24123
+ clientSecret;
24124
+ redirectUri;
24125
+ scopes;
24126
+ verify;
24127
+ postLogoutRedirectUri;
24128
+ claimMap;
24129
+ timeout;
24130
+ metadata = {};
24131
+ static mountedRouters = /* @__PURE__ */ new WeakSet();
24132
+ constructor(options = {}) {
24133
+ this.issuer = (options.issuer ?? process.env.TINA4_SSO_ISSUER ?? "").replace(/\/$/, "");
24134
+ this.clientId = options.clientId ?? process.env.TINA4_SSO_CLIENT_ID ?? "";
24135
+ this.clientSecret = options.clientSecret ?? process.env.TINA4_SSO_CLIENT_SECRET;
24136
+ this.redirectUri = options.redirectUri ?? process.env.TINA4_SSO_REDIRECT_URI ?? "";
24137
+ this.scopes = options.scopes ?? this.jsonEnv("TINA4_SSO_SCOPES", ["openid", "profile", "email"]);
24138
+ this.verify = options.verify ?? process.env.TINA4_SSO_VERIFY ?? "introspection";
24139
+ this.postLogoutRedirectUri = options.postLogoutRedirectUri ?? process.env.TINA4_SSO_POST_LOGOUT_REDIRECT_URI;
24140
+ this.claimMap = options.claimMap ?? this.jsonEnv("TINA4_SSO_CLAIM_MAP", {});
24141
+ this.timeout = options.timeout ?? 1e4;
24142
+ this.validateConfig();
24143
+ }
24144
+ static async fromIssuer(options = {}) {
24145
+ const value = new _Sso(options);
24146
+ await value.discover();
24147
+ return value;
24148
+ }
24149
+ static configured() {
24150
+ return ["TINA4_SSO_ISSUER", "TINA4_SSO_CLIENT_ID", "TINA4_SSO_REDIRECT_URI"].every((key) => Boolean(process.env[key]));
24151
+ }
24152
+ jsonEnv(name, fallback) {
24153
+ const raw = process.env[name];
24154
+ if (!raw) return fallback;
24155
+ try {
24156
+ return JSON.parse(raw);
24157
+ } catch {
24158
+ throw new SsoError(`${name} must be valid JSON`);
24159
+ }
24160
+ }
24161
+ static secureUrl(value, name) {
24162
+ let url;
24163
+ try {
24164
+ url = new URL(value);
24165
+ } catch {
24166
+ throw new SsoError(`${name} must be an absolute URL`);
24167
+ }
24168
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
24169
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
24170
+ throw new SsoError(`${name} must use HTTPS except on loopback`);
24171
+ }
24172
+ }
24173
+ validateConfig() {
24174
+ if (!this.issuer || !this.clientId || !this.redirectUri) {
24175
+ throw new SsoError("TINA4_SSO_ISSUER, TINA4_SSO_CLIENT_ID and TINA4_SSO_REDIRECT_URI are required");
24176
+ }
24177
+ _Sso.secureUrl(this.issuer, "issuer");
24178
+ _Sso.secureUrl(this.redirectUri, "redirect URI");
24179
+ if (!["introspection", "jwks"].includes(this.verify)) throw new SsoError("TINA4_SSO_VERIFY must be introspection or jwks");
24180
+ if (this.verify === "jwks") throw new SsoError("jwks verification requires an installed cryptography capability");
24181
+ if (this.verify === "introspection" && !this.clientSecret) throw new SsoError("introspection verification requires TINA4_SSO_CLIENT_SECRET");
24182
+ if (!Array.isArray(this.scopes) || !this.scopes.includes("openid")) throw new SsoError("TINA4_SSO_SCOPES must be a list containing openid");
24183
+ }
24184
+ async requestJson(url, form, bearer, basic = false) {
24185
+ const headers = { Accept: "application/json" };
24186
+ let body;
24187
+ if (form) {
24188
+ const parameters = new URLSearchParams();
24189
+ for (const [key, value] of Object.entries(form)) parameters.set(key, String(value));
24190
+ body = parameters.toString();
24191
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
24192
+ }
24193
+ if (bearer) headers.Authorization = `Bearer ${bearer}`;
24194
+ if (basic) headers.Authorization = `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64")}`;
24195
+ const controller = new AbortController();
24196
+ const timer = setTimeout(() => controller.abort(), this.timeout);
24197
+ try {
24198
+ const response = await fetch(url, { method: form ? "POST" : "GET", headers, body, signal: controller.signal });
24199
+ if (!response.ok) throw new SsoError("OIDC provider request failed");
24200
+ const result = await response.json();
24201
+ if (!result || typeof result !== "object" || Array.isArray(result)) throw new SsoError("OIDC provider returned a non-object response");
24202
+ return result;
24203
+ } catch (error) {
24204
+ if (error instanceof SsoError) throw error;
24205
+ throw new SsoError("OIDC provider request failed");
24206
+ } finally {
24207
+ clearTimeout(timer);
24208
+ }
24209
+ }
24210
+ async discover(force = false) {
24211
+ if (Object.keys(this.metadata).length && !force) return { ...this.metadata };
24212
+ const result = await this.requestJson(`${this.issuer}/.well-known/openid-configuration`);
24213
+ if (result.issuer !== this.issuer) throw new SsoError("OIDC discovery issuer does not exactly match configuration");
24214
+ const required = ["authorization_endpoint", "token_endpoint"];
24215
+ if (this.verify === "introspection") required.push("introspection_endpoint");
24216
+ for (const key of required) {
24217
+ if (!result[key]) throw new SsoError(`OIDC discovery is missing ${key}`);
24218
+ _Sso.secureUrl(result[key], key);
24219
+ }
24220
+ this.metadata = result;
24221
+ return { ...result };
24222
+ }
24223
+ static safeReturn(value) {
24224
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\\")) return "/";
24225
+ return [...value].some((char) => char.charCodeAt(0) < 32) ? "/" : value;
24226
+ }
24227
+ session(value) {
24228
+ return value?.session ?? value;
24229
+ }
24230
+ async login(requestOrSession, returnTo = "/") {
24231
+ const session = this.session(requestOrSession);
24232
+ if (!session) throw new SsoError("SSO login requires a Tina4 Session");
24233
+ const state = randomBytes5(32).toString("base64url");
24234
+ const nonce = randomBytes5(32).toString("base64url");
24235
+ const verifier = randomBytes5(64).toString("base64url");
24236
+ const challenge = createHash9("sha256").update(verifier).digest("base64url");
24237
+ session.set(_Sso.PENDING_KEY, { state, nonce, verifier, return_to: _Sso.safeReturn(returnTo), created_at: Math.floor(Date.now() / 1e3) });
24238
+ const metadata = await this.discover();
24239
+ const query = new URLSearchParams({
24240
+ client_id: this.clientId,
24241
+ redirect_uri: this.redirectUri,
24242
+ response_type: "code",
24243
+ scope: this.scopes.join(" "),
24244
+ state,
24245
+ nonce,
24246
+ code_challenge: challenge,
24247
+ code_challenge_method: "S256"
24248
+ });
24249
+ return `${metadata.authorization_endpoint}?${query}`;
24250
+ }
24251
+ static equal(left, right) {
24252
+ const a = Buffer.from(String(left ?? ""));
24253
+ const b = Buffer.from(String(right ?? ""));
24254
+ return a.length === b.length && timingSafeEqual3(a, b);
24255
+ }
24256
+ static jwtPayload(token) {
24257
+ try {
24258
+ return JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
24259
+ } catch {
24260
+ throw new SsoError("provider returned an invalid ID token");
24261
+ }
24262
+ }
24263
+ async introspect(accessToken) {
24264
+ const metadata = await this.discover();
24265
+ const result = await this.requestJson(metadata.introspection_endpoint, { token: accessToken, token_type_hint: "access_token" }, void 0, true);
24266
+ if (result.active !== true || result.iss !== this.issuer) throw new SsoError("OIDC access token is inactive or has the wrong issuer");
24267
+ const audience = result.aud ?? result.client_id;
24268
+ const valid = (Array.isArray(audience) ? audience.includes(this.clientId) : audience === this.clientId) || result.client_id === this.clientId;
24269
+ if (!valid) throw new SsoError("OIDC token audience mismatch");
24270
+ return result;
24271
+ }
24272
+ claim(claims, configured, fallback) {
24273
+ let value = claims;
24274
+ for (const part of (configured ?? fallback).split(".")) value = value && typeof value === "object" ? value[part] : void 0;
24275
+ return value;
24276
+ }
24277
+ normalize(claims) {
24278
+ const subject = this.claim(claims, this.claimMap.subject, "sub");
24279
+ const issuer = this.claim(claims, this.claimMap.issuer, "iss") ?? this.issuer;
24280
+ if (!subject || issuer !== this.issuer) throw new SsoError("OIDC identity is missing a valid issuer or subject");
24281
+ const roles = [...this.claim(claims, this.claimMap.roles, "realm_access.roles") ?? [], ...claims.resource_access?.[this.clientId]?.roles ?? []];
24282
+ const groups = this.claim(claims, this.claimMap.groups, "groups") ?? [];
24283
+ return {
24284
+ issuer,
24285
+ subject,
24286
+ username: this.claim(claims, this.claimMap.username, "preferred_username") ?? null,
24287
+ email: this.claim(claims, this.claimMap.email, "email") ?? null,
24288
+ name: this.claim(claims, this.claimMap.name, "name") ?? null,
24289
+ roles: [...new Set(roles.map(String))].sort(),
24290
+ groups: [...new Set(groups.map(String))].sort()
24291
+ };
24292
+ }
24293
+ async callback(requestOrSession, query) {
24294
+ const session = this.session(requestOrSession);
24295
+ const values = query ?? requestOrSession?.query ?? {};
24296
+ const pending = session?.get(_Sso.PENDING_KEY);
24297
+ session?.delete(_Sso.PENDING_KEY);
24298
+ if (!pending || !values.code || !_Sso.equal(values.state, pending.state)) throw new SsoError("OIDC callback state is invalid or already consumed");
24299
+ if (Math.floor(Date.now() / 1e3) - Number(pending.created_at ?? 0) > 600) throw new SsoError("OIDC callback state has expired");
24300
+ const metadata = await this.discover();
24301
+ const tokens = await this.requestJson(metadata.token_endpoint, {
24302
+ grant_type: "authorization_code",
24303
+ code: values.code,
24304
+ redirect_uri: this.redirectUri,
24305
+ client_id: this.clientId,
24306
+ code_verifier: pending.verifier
24307
+ }, void 0, Boolean(this.clientSecret));
24308
+ if (!tokens.access_token || !tokens.id_token) throw new SsoError("OIDC token response is incomplete");
24309
+ if (this.verify === "jwks") throw new SsoError("JWKS verification requires an installed cryptography capability");
24310
+ const claims = await this.introspect(tokens.access_token);
24311
+ if (!_Sso.equal(_Sso.jwtPayload(tokens.id_token).nonce, pending.nonce)) throw new SsoError("OIDC ID token nonce mismatch");
24312
+ if (metadata.userinfo_endpoint) Object.assign(claims, await this.requestJson(metadata.userinfo_endpoint, void 0, tokens.access_token));
24313
+ const identity = this.normalize(claims);
24314
+ session.regenerate();
24315
+ session.set(_Sso.SESSION_KEY, {
24316
+ version: 1,
24317
+ identity,
24318
+ access_token: tokens.access_token,
24319
+ refresh_token: tokens.refresh_token,
24320
+ id_token: tokens.id_token,
24321
+ expires_at: Math.floor(Date.now() / 1e3) + Number(tokens.expires_in ?? 0)
24322
+ });
24323
+ return { identity, return_to: _Sso.safeReturn(pending.return_to) };
24324
+ }
24325
+ identity(requestOrSession) {
24326
+ const stored = this.session(requestOrSession)?.get(_Sso.SESSION_KEY);
24327
+ const identity = stored?.identity ?? null;
24328
+ if (identity && requestOrSession?.session) requestOrSession.user = identity;
24329
+ return identity;
24330
+ }
24331
+ async refresh(requestOrSession) {
24332
+ const session = this.session(requestOrSession);
24333
+ const stored = session?.get(_Sso.SESSION_KEY);
24334
+ if (!stored?.refresh_token) {
24335
+ session?.delete(_Sso.SESSION_KEY);
24336
+ throw new SsoError("OIDC session cannot be refreshed");
24337
+ }
24338
+ try {
24339
+ const metadata = await this.discover();
24340
+ const tokens = await this.requestJson(metadata.token_endpoint, {
24341
+ grant_type: "refresh_token",
24342
+ refresh_token: stored.refresh_token,
24343
+ client_id: this.clientId
24344
+ }, void 0, Boolean(this.clientSecret));
24345
+ const claims = await this.introspect(tokens.access_token);
24346
+ if (metadata.userinfo_endpoint) Object.assign(claims, await this.requestJson(metadata.userinfo_endpoint, void 0, tokens.access_token));
24347
+ const identity = this.normalize(claims);
24348
+ session.set(_Sso.SESSION_KEY, {
24349
+ ...stored,
24350
+ identity,
24351
+ access_token: tokens.access_token,
24352
+ refresh_token: tokens.refresh_token ?? stored.refresh_token,
24353
+ id_token: tokens.id_token ?? stored.id_token,
24354
+ expires_at: Math.floor(Date.now() / 1e3) + Number(tokens.expires_in ?? 0)
24355
+ });
24356
+ return identity;
24357
+ } catch (error) {
24358
+ session?.delete(_Sso.SESSION_KEY);
24359
+ throw error;
24360
+ }
24361
+ }
24362
+ async logout(requestOrSession, returnTo = "/") {
24363
+ const session = this.session(requestOrSession);
24364
+ const stored = session?.get(_Sso.SESSION_KEY);
24365
+ session?.destroy();
24366
+ const endpoint = (await this.discover()).end_session_endpoint;
24367
+ const target = this.postLogoutRedirectUri ?? _Sso.safeReturn(returnTo);
24368
+ if (!endpoint) return target;
24369
+ const params = new URLSearchParams({ post_logout_redirect_uri: target, client_id: this.clientId });
24370
+ if (stored?.id_token) params.set("id_token_hint", stored.id_token);
24371
+ return `${endpoint}?${params}`;
24372
+ }
24373
+ static async mountConfigured(router) {
24374
+ if (_Sso.mountedRouters.has(router) || !_Sso.configured()) return false;
24375
+ const owned = /* @__PURE__ */ new Set(["GET /auth/login", "GET /auth/callback", "POST /auth/logout"]);
24376
+ const collisions = router.getRoutes().map((route) => `${route.method} ${route.pattern}`).filter((route) => owned.has(route));
24377
+ if (collisions.length) throw new SsoError(`SSO route collision: ${collisions.join(", ")}`);
24378
+ const sso = await _Sso.fromIssuer();
24379
+ router.get("/auth/login", async (req2, res) => res.redirect(await sso.login(req2, req2.query?.return_to ?? "/")));
24380
+ router.get("/auth/callback", async (req2, res) => {
24381
+ try {
24382
+ return res.redirect((await sso.callback(req2)).return_to);
24383
+ } catch (error) {
24384
+ const message = error instanceof SsoError ? error.message : "OIDC callback failed";
24385
+ return res.error("SSO_CALLBACK_FAILED", message, 400);
24386
+ }
24387
+ });
24388
+ router.post("/auth/logout", async (req2, res) => res.redirect(await sso.logout(req2, req2.query?.return_to ?? "/")));
24389
+ _Sso.mountedRouters.add(router);
24390
+ return true;
24391
+ }
24392
+ };
24393
+ }
24394
+ });
24395
+
23822
24396
  // ../core/src/docsAutoDiscovery.ts
23823
24397
  var docsAutoDiscovery_exports = {};
23824
24398
  __export(docsAutoDiscovery_exports, {
@@ -23888,7 +24462,7 @@ var init_docsAutoDiscovery = __esm({
23888
24462
 
23889
24463
  // ../core/src/server.ts
23890
24464
  import { createServer as createServer2 } from "node:http";
23891
- import { randomBytes as randomBytes5 } from "node:crypto";
24465
+ import { randomBytes as randomBytes6 } from "node:crypto";
23892
24466
  import { resolve as resolve14, dirname as dirname11, join as join24, relative as relative8 } from "node:path";
23893
24467
  import { existsSync as existsSync22, readdirSync as readdirSync14, readFileSync as readFileSync21, statSync as statSync15 } from "node:fs";
23894
24468
  import { isatty } from "node:tty";
@@ -24528,7 +25102,7 @@ async function renderDispatchError(err, req2, res, templatesDir) {
24528
25102
  }
24529
25103
  }
24530
25104
  }
24531
- const requestId = Log.getRequestId() ?? randomBytes5(4).toString("hex");
25105
+ const requestId = Log.getRequestId() ?? randomBytes6(4).toString("hex");
24532
25106
  if (wantsJson(req2)) {
24533
25107
  const body = negotiatedErrorBody(500, "Internal Server Error", requestId);
24534
25108
  res.raw.writeHead(500, { "Content-Type": "application/json" });
@@ -24599,7 +25173,7 @@ function serveStaticAsset(ctx) {
24599
25173
  return false;
24600
25174
  }
24601
25175
  async function serveNotFound(ctx) {
24602
- const requestId = Log.getRequestId() ?? randomBytes5(4).toString("hex");
25176
+ const requestId = Log.getRequestId() ?? randomBytes6(4).toString("hex");
24603
25177
  if (wantsJson(ctx.req)) {
24604
25178
  const body = negotiatedErrorBody(404, "Not Found", requestId);
24605
25179
  ctx.res.raw.writeHead(404, httpReason(404), { "Content-Type": "application/json" });
@@ -24719,7 +25293,7 @@ async function dispatchInner(ctx, rawReq, rawRes, requestId) {
24719
25293
  }
24720
25294
  }
24721
25295
  async function runDispatch(ctx, rawReq, rawRes) {
24722
- const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes5(4).toString("hex");
25296
+ const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes6(4).toString("hex");
24723
25297
  if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
24724
25298
  return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
24725
25299
  }
@@ -24853,6 +25427,8 @@ ${reset2}
24853
25427
  console.log(`
24854
25428
  No routes directory found at ${routesDir}`);
24855
25429
  }
25430
+ const { Sso: Sso2 } = await Promise.resolve().then(() => (init_sso(), sso_exports));
25431
+ await Sso2.mountConfigured(router);
24856
25432
  if (attachCsrfFromEnv()) {
24857
25433
  console.log(`
24858
25434
  \x1B[36mCSRF\x1B[0m protection enabled (TINA4_CSRF)`);
@@ -25845,7 +26421,7 @@ var init_mqttMessage = __esm({
25845
26421
  // ../core/src/mqtt.ts
25846
26422
  import net2 from "node:net";
25847
26423
  import tls from "node:tls";
25848
- import { randomBytes as randomBytes6 } from "node:crypto";
26424
+ import { randomBytes as randomBytes7 } from "node:crypto";
25849
26425
  import { existsSync as existsSync24, readFileSync as readFileSync22 } from "node:fs";
25850
26426
  var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
25851
26427
  var init_mqtt = __esm({
@@ -25933,7 +26509,7 @@ var init_mqtt = __esm({
25933
26509
  this.caFile = options.caFile ?? (Env.str("TINA4_MQTT_CA_FILE") || null);
25934
26510
  this.tlsVerify = options.tlsVerify ?? Env.bool("TINA4_MQTT_TLS_VERIFY", true);
25935
26511
  let cid = options.clientId ?? (Env.str("TINA4_MQTT_CLIENT_ID") || null);
25936
- if (cid === null || cid === "") cid = "tina4-" + randomBytes6(8).toString("hex");
26512
+ if (cid === null || cid === "") cid = "tina4-" + randomBytes7(8).toString("hex");
25937
26513
  this.clientId = cid;
25938
26514
  this.keepalive = options.keepalive ?? Env.int("TINA4_MQTT_KEEPALIVE", DEFAULT_KEEPALIVE);
25939
26515
  this.cleanSession = options.cleanSession ?? true;
@@ -26863,7 +27439,7 @@ var init_service = __esm({
26863
27439
  import http from "node:http";
26864
27440
  import https from "node:https";
26865
27441
  import { URL as URL2 } from "node:url";
26866
- import { randomBytes as randomBytes7 } from "node:crypto";
27442
+ import { randomBytes as randomBytes8 } from "node:crypto";
26867
27443
  import { promises as fsp, createWriteStream } from "node:fs";
26868
27444
  import { basename as basename5 } from "node:path";
26869
27445
  import { pipeline } from "node:stream/promises";
@@ -27161,7 +27737,7 @@ var init_api = __esm({
27161
27737
  return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
27162
27738
  }
27163
27739
  const partContentType = guessContentType(uploadName);
27164
- const boundary = "----Tina4Boundary" + randomBytes7(16).toString("hex");
27740
+ const boundary = "----Tina4Boundary" + randomBytes8(16).toString("hex");
27165
27741
  const bodyBuffer = buildMultipartBody(boundary, fieldName, uploadName, content, partContentType, extraFields);
27166
27742
  const contentType = `multipart/form-data; boundary=${boundary}`;
27167
27743
  return this.execute("POST", this.buildUrl(path8), bodyBuffer, contentType, headers);
@@ -31993,10 +32569,13 @@ __export(src_exports2, {
31993
32569
  RouteGroup: () => RouteGroup,
31994
32570
  RouteRef: () => RouteRef,
31995
32571
  Router: () => Router,
32572
+ SSO: () => Sso,
31996
32573
  SafeString: () => SafeString2,
31997
32574
  SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
31998
32575
  ServiceRunner: () => ServiceRunner,
31999
32576
  Session: () => Session,
32577
+ Sso: () => Sso,
32578
+ SsoError: () => SsoError,
32000
32579
  TAKEOVER_KILLED: () => TAKEOVER_KILLED,
32001
32580
  TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
32002
32581
  TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
@@ -32233,6 +32812,7 @@ var init_src2 = __esm({
32233
32812
  init_htmlElement();
32234
32813
  init_errorOverlay();
32235
32814
  init_ai();
32815
+ init_sso();
32236
32816
  init_aiClient();
32237
32817
  init_liteBackend();
32238
32818
  init_rabbitmqBackend();
@@ -33331,6 +33911,8 @@ function fieldTypeToPostgres(def) {
33331
33911
  return "TEXT";
33332
33912
  case "json":
33333
33913
  return "JSONB";
33914
+ case "point":
33915
+ return SQLTranslator.pointColumnType("postgres", def.srid ?? 4326);
33334
33916
  case "string":
33335
33917
  return def.maxLength ? `VARCHAR(${def.maxLength})` : "VARCHAR(255)";
33336
33918
  default:
@@ -37847,7 +38429,9 @@ function buildAddColumnSql(adapter, table2, colName, def) {
37847
38429
  return sql;
37848
38430
  }
37849
38431
  function mt(db) {
37850
- return engineOf(db) === "mysql" ? `\`${MIGRATION_TABLE}\`` : `"${MIGRATION_TABLE}"`;
38432
+ const engine = engineOf(db);
38433
+ if (engine === "firebird") return MIGRATION_TABLE;
38434
+ return engine === "mysql" ? `\`${MIGRATION_TABLE}\`` : `"${MIGRATION_TABLE}"`;
37851
38435
  }
37852
38436
  function deriveDescription(name) {
37853
38437
  return name.replace(/^\d+_/, "").replace(/_/g, " ");
@@ -37870,9 +38454,9 @@ async function ensureMigrationTableOn(db) {
37870
38454
  id INTEGER NOT NULL PRIMARY KEY,
37871
38455
  migration_name VARCHAR(500) NOT NULL UNIQUE,
37872
38456
  description VARCHAR(500),
37873
- batch INTEGER NOT NULL DEFAULT 1,
38457
+ batch INTEGER DEFAULT 1 NOT NULL,
37874
38458
  executed_at VARCHAR(50) NOT NULL,
37875
- passed INTEGER NOT NULL DEFAULT 1
38459
+ passed INTEGER DEFAULT 1 NOT NULL
37876
38460
  )`);
37877
38461
  } else {
37878
38462
  const idCol = migrationIdColumn(db);
@@ -37969,7 +38553,7 @@ async function recordApplied(db, name, batch, passed = 1) {
37969
38553
  "SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS NEXT_ID FROM RDB$DATABASE"
37970
38554
  );
37971
38555
  insertCols.unshift("id");
37972
- values.unshift(rows[0]?.NEXT_ID ?? 1);
38556
+ values.unshift(rows[0]?.next_id ?? 1);
37973
38557
  }
37974
38558
  const placeholders = insertCols.map(() => "?").join(", ");
37975
38559
  await adapterExecute(
@@ -38954,10 +39538,13 @@ var init_queryBuilder = __esm({
38954
39538
  "use strict";
38955
39539
  init_database();
38956
39540
  init_databaseResult();
39541
+ init_point();
39542
+ init_sqlTranslator();
38957
39543
  QueryBuilder = class _QueryBuilder {
38958
39544
  table;
38959
39545
  db;
38960
39546
  columns = ["*"];
39547
+ selectParams = [];
38961
39548
  wheres = [];
38962
39549
  params = [];
38963
39550
  joinClauses = [];
@@ -38965,14 +39552,17 @@ var init_queryBuilder = __esm({
38965
39552
  havings = [];
38966
39553
  havingParams = [];
38967
39554
  orderByCols = [];
39555
+ orderByParams = [];
39556
+ primaryKey;
38968
39557
  limitVal;
38969
39558
  offsetVal;
38970
39559
  /**
38971
39560
  * Private constructor — use static factory methods.
38972
39561
  */
38973
- constructor(table2, db) {
39562
+ constructor(table2, db, primaryKey) {
38974
39563
  this.table = table2;
38975
39564
  this.db = db;
39565
+ this.primaryKey = primaryKey;
38976
39566
  }
38977
39567
  /**
38978
39568
  * Create a QueryBuilder for a table.
@@ -38981,8 +39571,8 @@ var init_queryBuilder = __esm({
38981
39571
  * @param db - Optional database adapter.
38982
39572
  * @returns A new QueryBuilder instance.
38983
39573
  */
38984
- static fromTable(tableName, db) {
38985
- return new _QueryBuilder(tableName, db);
39574
+ static fromTable(tableName, db, primaryKey) {
39575
+ return new _QueryBuilder(tableName, db, primaryKey);
38986
39576
  }
38987
39577
  /**
38988
39578
  * Set the columns to select.
@@ -38993,6 +39583,7 @@ var init_queryBuilder = __esm({
38993
39583
  select(...cols) {
38994
39584
  if (cols.length > 0) {
38995
39585
  this.columns = cols;
39586
+ this.selectParams = [];
38996
39587
  }
38997
39588
  return this;
38998
39589
  }
@@ -39074,6 +39665,41 @@ var init_queryBuilder = __esm({
39074
39665
  this.orderByCols.push(expression);
39075
39666
  return this;
39076
39667
  }
39668
+ withinDistance(column2, pointValue, radiusMetres, srid = DEFAULT_SRID) {
39669
+ const radius = Number(radiusMetres);
39670
+ if (!Number.isFinite(radius) || radius < 0) throw new RangeError("Spatial radius must be finite and greater than or equal to zero");
39671
+ const point = Point.parse(pointValue, srid);
39672
+ return this.where(SQLTranslator.withinDistance(this.engine(), column2, point.srid), [point.lon, point.lat, radius]);
39673
+ }
39674
+ intersects(column2, geometry, srid = DEFAULT_SRID) {
39675
+ const [bound, form] = Point.geometryBinding(geometry, srid);
39676
+ return this.where(SQLTranslator.intersects(this.engine(), column2, form, srid), [bound]);
39677
+ }
39678
+ bbox(column2, minLon, minLat, maxLon, maxLat, srid = DEFAULT_SRID) {
39679
+ const values = [minLon, minLat, maxLon, maxLat].map(Number);
39680
+ if (!values.every(Number.isFinite)) throw new TypeError("Bounding-box coordinates must be finite numbers");
39681
+ const [west, south, east, north] = values;
39682
+ new Point(west, south, srid);
39683
+ new Point(east, north, srid);
39684
+ if (west > east || south > north) throw new RangeError("Bounding box must be ordered west, south, east, north");
39685
+ return this.where(SQLTranslator.bbox(this.engine(), column2, srid), values);
39686
+ }
39687
+ selectDistance(column2, pointValue, alias = "distance", srid = DEFAULT_SRID) {
39688
+ const point = Point.parse(pointValue, srid);
39689
+ this.columns.push(SQLTranslator.distanceAs(this.engine(), column2, alias, point.srid));
39690
+ this.selectParams.push(point.lon, point.lat);
39691
+ return this;
39692
+ }
39693
+ orderByDistance(column2, pointValue, direction = "ASC", srid = DEFAULT_SRID) {
39694
+ const order = direction.toUpperCase();
39695
+ if (order !== "ASC" && order !== "DESC") throw new TypeError("Distance order direction must be ASC or DESC");
39696
+ if (!this.primaryKey) throw new Error("Stable spatial ordering needs a primary key; use BaseModel.query() or pass one to fromTable()");
39697
+ const point = Point.parse(pointValue, srid);
39698
+ this.orderByCols.push(`${SQLTranslator.distance(this.engine(), column2, point.srid)} ${order}`);
39699
+ this.orderByParams.push(point.lon, point.lat);
39700
+ this.orderByCols.push(`${SQLTranslator.spatialIdentifier(this.primaryKey, "primary key")} ASC`);
39701
+ return this;
39702
+ }
39077
39703
  /**
39078
39704
  * Set LIMIT and optional OFFSET.
39079
39705
  *
@@ -39139,7 +39765,7 @@ var init_queryBuilder = __esm({
39139
39765
  async get() {
39140
39766
  this.ensureDb();
39141
39767
  const sql = this.toSql();
39142
- const allParams = [...this.params, ...this.havingParams];
39768
+ const allParams = [...this.selectParams, ...this.params, ...this.havingParams, ...this.orderByParams];
39143
39769
  const queryParams = allParams.length > 0 ? allParams : void 0;
39144
39770
  const rows = await adapterFetch(
39145
39771
  this.db,
@@ -39167,7 +39793,7 @@ var init_queryBuilder = __esm({
39167
39793
  async first() {
39168
39794
  this.ensureDb();
39169
39795
  const sql = this.toSql();
39170
- const allParams = [...this.params, ...this.havingParams];
39796
+ const allParams = [...this.selectParams, ...this.params, ...this.havingParams, ...this.orderByParams];
39171
39797
  return adapterFetchOne(
39172
39798
  this.db,
39173
39799
  sql,
@@ -39182,9 +39808,18 @@ var init_queryBuilder = __esm({
39182
39808
  async count() {
39183
39809
  this.ensureDb();
39184
39810
  const original = this.columns;
39811
+ const originalSelectParams = this.selectParams;
39812
+ const originalOrder = this.orderByCols;
39813
+ const originalOrderParams = this.orderByParams;
39185
39814
  this.columns = ["COUNT(*) as cnt"];
39815
+ this.selectParams = [];
39816
+ this.orderByCols = [];
39817
+ this.orderByParams = [];
39186
39818
  const sql = this.toSql();
39187
39819
  this.columns = original;
39820
+ this.selectParams = originalSelectParams;
39821
+ this.orderByCols = originalOrder;
39822
+ this.orderByParams = originalOrderParams;
39188
39823
  const allParams = [...this.params, ...this.havingParams];
39189
39824
  const row = await adapterFetchOne(
39190
39825
  this.db,
@@ -39369,6 +40004,10 @@ var init_queryBuilder = __esm({
39369
40004
  }
39370
40005
  }
39371
40006
  }
40007
+ engine() {
40008
+ this.ensureDb();
40009
+ return this.db.getDatabaseType();
40010
+ }
39372
40011
  };
39373
40012
  }
39374
40013
  });
@@ -39392,6 +40031,11 @@ function toDbFieldValue(def, value) {
39392
40031
  if (def?.type === "json" && value !== null && value !== void 0 && typeof value !== "string") {
39393
40032
  return JSON.stringify(value);
39394
40033
  }
40034
+ if (def?.type === "point" && value !== null && value !== void 0) {
40035
+ const point = Point.parse(value, def.srid ?? DEFAULT_SRID);
40036
+ if (point.srid !== (def.srid ?? DEFAULT_SRID)) throw new TypeError(`Point field expects SRID ${def.srid ?? DEFAULT_SRID}; received ${point.srid}`);
40037
+ return point.ewkt;
40038
+ }
39395
40039
  return value;
39396
40040
  }
39397
40041
  function fromDbFieldValue(def, value) {
@@ -39402,6 +40046,11 @@ function fromDbFieldValue(def, value) {
39402
40046
  return value;
39403
40047
  }
39404
40048
  }
40049
+ if (def?.type === "point" && value !== null && value !== void 0) {
40050
+ const point = Point.parse(value, def.srid ?? DEFAULT_SRID);
40051
+ if (point.srid !== (def.srid ?? DEFAULT_SRID)) throw new TypeError(`Point field expects SRID ${def.srid ?? DEFAULT_SRID}; received ${point.srid}`);
40052
+ return point;
40053
+ }
39405
40054
  return value;
39406
40055
  }
39407
40056
  function _pluralRelKeys() {
@@ -39437,6 +40086,7 @@ var init_baseModel = __esm({
39437
40086
  init_sqlite();
39438
40087
  init_sqlTranslator();
39439
40088
  init_src2();
40089
+ init_point();
39440
40090
  _fkRegistry = /* @__PURE__ */ new Map();
39441
40091
  EAGER_IN_CHUNK = 500;
39442
40092
  modelQueryCache = new QueryCache({ defaultTtl: 0, maxSize: 500 });
@@ -39492,7 +40142,9 @@ var init_baseModel = __esm({
39492
40142
  for (const [name, def] of Object.entries(fields0)) {
39493
40143
  if (def.default === void 0) continue;
39494
40144
  let dv = typeof def.default === "function" ? def.default() : def.default;
39495
- if (dv !== null && typeof dv === "object") dv = structuredClone(dv);
40145
+ if (def.type === "point" && dv !== null && dv !== void 0) {
40146
+ dv = fromDbFieldValue(def, dv);
40147
+ } else if (dv !== null && typeof dv === "object") dv = structuredClone(dv);
39496
40148
  this[name] = dv;
39497
40149
  }
39498
40150
  if (data) {
@@ -39594,7 +40246,7 @@ var init_baseModel = __esm({
39594
40246
  * @returns A QueryBuilder instance bound to this model's table and database.
39595
40247
  */
39596
40248
  static query() {
39597
- return QueryBuilder.fromTable(this.tableName, this.getDb());
40249
+ return QueryBuilder.fromTable(this.tableName, this.getDb(), this.getPkColumn());
39598
40250
  }
39599
40251
  /**
39600
40252
  * Get the database adapter for this model.
@@ -40047,7 +40699,7 @@ var init_baseModel = __esm({
40047
40699
  for (const key of Object.keys(ModelClass.fields)) {
40048
40700
  if (this[key] !== void 0) {
40049
40701
  const outKey = case_ === "snake" ? ModelClass.fieldMapping[key] ?? key : key;
40050
- result[outKey] = this[key];
40702
+ result[outKey] = this[key] instanceof Point ? this[key].geojson : this[key];
40051
40703
  }
40052
40704
  }
40053
40705
  if (ModelClass.softDelete && this.is_deleted !== void 0) {
@@ -40107,6 +40759,19 @@ var init_baseModel = __esm({
40107
40759
  }
40108
40760
  return result;
40109
40761
  }
40762
+ toFeature(geometryField, include) {
40763
+ const ModelClass = this.constructor;
40764
+ const pointFields = Object.entries(ModelClass.fields).filter(([, def]) => def.type === "point").map(([name]) => name);
40765
+ const field = geometryField ?? pointFields[0];
40766
+ if (!field || !pointFields.includes(field)) throw new Error("toFeature() needs a declared point field");
40767
+ const properties = this.toDict(include, "camel");
40768
+ const geometry = properties[field] ?? null;
40769
+ delete properties[field];
40770
+ return { type: "Feature", geometry, properties };
40771
+ }
40772
+ static featureCollection(models, geometryField, include) {
40773
+ return { type: "FeatureCollection", features: models.map((model) => model.toFeature(geometryField, include)) };
40774
+ }
40110
40775
  /**
40111
40776
  * Convert to an associative object (alias for toDict).
40112
40777
  */
@@ -40157,7 +40822,10 @@ var init_baseModel = __esm({
40157
40822
  */
40158
40823
  static async createTable() {
40159
40824
  const db = this.getDb();
40160
- if (await adapterTableExists(db, this.tableName)) return true;
40825
+ const pointFields = Object.entries(this.fields).filter(([, def]) => def.type === "point");
40826
+ const engine = db.getDatabaseType();
40827
+ if (pointFields.length > 0) SQLTranslator.requireSpatial(engine, "PointField");
40828
+ if (await adapterTableExists(db, this.tableName)) return this.createSpatialIndexes(db, pointFields);
40161
40829
  if (typeof db.createTable === "function" || typeof db.createTableAsync === "function") {
40162
40830
  const mappedFields = {};
40163
40831
  for (const [fieldName, def] of Object.entries(this.fields)) {
@@ -40173,7 +40841,7 @@ var init_baseModel = __esm({
40173
40841
  mappedFields["is_deleted"] = { type: "integer", default: 0 };
40174
40842
  }
40175
40843
  await adapterCreateTable(db, this.tableName, mappedFields);
40176
- return true;
40844
+ return this.createSpatialIndexes(db, pointFields);
40177
40845
  }
40178
40846
  const typeMap = {
40179
40847
  integer: "INTEGER",
@@ -40220,6 +40888,14 @@ var init_baseModel = __esm({
40220
40888
  }
40221
40889
  return true;
40222
40890
  }
40891
+ static async createSpatialIndexes(db, fields) {
40892
+ for (const [fieldName, def] of fields) {
40893
+ SQLTranslator.pointColumnType(db.getDatabaseType(), def.srid ?? DEFAULT_SRID);
40894
+ if (def.spatialIndex === false) continue;
40895
+ await adapterExecute(db, SQLTranslator.spatialIndex(db.getDatabaseType(), this.tableName, this.getDbColumn(fieldName)));
40896
+ }
40897
+ return true;
40898
+ }
40223
40899
  /**
40224
40900
  * Find a record by primary key or throw an error if not found.
40225
40901
  */
@@ -40291,15 +40967,25 @@ var init_baseModel = __esm({
40291
40967
  /**
40292
40968
  * Invalidate every cached query that touches this model's table.
40293
40969
  *
40294
- * Tag-scoped, NOT a wholesale flush: a cached JOIN on another model that reads
40295
- * this table is busted too (it carries this table's tag), while a query that
40296
- * never touches this table is left intact. Called after every ORM write
40297
- * (save/delete/forceDelete/restore) so a read-after-write never serves a
40298
- * stale/deleted row (CACHE-DEC-01).
40970
+ * Tag-scoped in the ORM layer (a cached JOIN on another model that reads
40971
+ * this table is busted too because it carries this table's tag; a query
40972
+ * that never touches this table is left intact), then cascaded to the
40973
+ * DB layer on this model's bound connection so an out-of-band write /
40974
+ * deliberate refresh / race-with-another-process cannot leave stale rows
40975
+ * in db.fetch()'s persistent cache. Called after every ORM write
40976
+ * (save/delete/forceDelete/restore) so a read-after-write never serves
40977
+ * a stale/deleted row (CACHE-DEC-01). PY-06-22 (3.13.105) added the
40978
+ * DB-layer cascade -- previously the two cache layers disagreed under
40979
+ * TINA4_AUTO_CACHING=true + TINA4_DB_CACHE=true.
40299
40980
  */
40300
40981
  static clearCache() {
40301
40982
  const ModelClass = this;
40302
40983
  modelQueryCache.clearTag((ModelClass.tableName ?? "").toLowerCase());
40984
+ try {
40985
+ const db = ModelClass.getDb();
40986
+ if (typeof db?.cacheClear === "function") db.cacheClear();
40987
+ } catch {
40988
+ }
40303
40989
  }
40304
40990
  /**
40305
40991
  * Execute a raw SQL SELECT and return results as model instances.
@@ -41159,7 +41845,7 @@ var init_seeder = __esm({
41159
41845
 
41160
41846
  // src/docstore.ts
41161
41847
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
41162
- import { randomBytes as randomBytes8 } from "node:crypto";
41848
+ import { randomBytes as randomBytes9 } from "node:crypto";
41163
41849
  import { mkdirSync as mkdirSync19 } from "node:fs";
41164
41850
  import { dirname as dirname14, isAbsolute as isAbsolute6, join as join31 } from "node:path";
41165
41851
  function iso(d) {
@@ -41511,8 +42197,8 @@ var init_docstore = __esm({
41511
42197
  OID_RE = /^[0-9a-fA-F]{24}$/;
41512
42198
  ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/;
41513
42199
  ObjectId = class _ObjectId {
41514
- static _counter = randomBytes8(3).readUIntBE(0, 3);
41515
- static _process = randomBytes8(5);
42200
+ static _counter = randomBytes9(3).readUIntBE(0, 3);
42201
+ static _process = randomBytes9(5);
41516
42202
  _bytes;
41517
42203
  constructor(oid) {
41518
42204
  if (oid === void 0 || oid === null) {
@@ -41940,7 +42626,7 @@ var init_attachment = __esm({
41940
42626
  });
41941
42627
 
41942
42628
  // src/realtime/storage.ts
41943
- import { randomBytes as randomBytes9 } from "node:crypto";
42629
+ import { randomBytes as randomBytes10 } from "node:crypto";
41944
42630
  import { mkdirSync as mkdirSync20, readFileSync as readFileSync26, writeFileSync as writeFileSync17, unlinkSync as unlinkSync8, statSync as statSync18 } from "node:fs";
41945
42631
  import { resolve as resolve19, sep as sep5 } from "node:path";
41946
42632
  import { createRequire as createRequire8 } from "node:module";
@@ -41951,7 +42637,7 @@ function storageKey(filename = "") {
41951
42637
  const clean = raw.replace(UNSAFE, "").slice(0, 12);
41952
42638
  if (clean) ext = `.${clean}`;
41953
42639
  }
41954
- return `${randomBytes9(16).toString("hex")}${ext}`;
42640
+ return `${randomBytes10(16).toString("hex")}${ext}`;
41955
42641
  }
41956
42642
  function selectStorage(storage) {
41957
42643
  if (storage) return storage;
@@ -42399,6 +43085,7 @@ __export(index_exports, {
42399
43085
  CachedDatabaseAdapter: () => CachedDatabaseAdapter,
42400
43086
  Cursor: () => Cursor,
42401
43087
  DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
43088
+ DEFAULT_SRID: () => DEFAULT_SRID,
42402
43089
  Database: () => Database,
42403
43090
  DatabaseResult: () => DatabaseResult,
42404
43091
  DatabaseUrl: () => DatabaseUrl,
@@ -42414,6 +43101,7 @@ __export(index_exports, {
42414
43101
  NOT_REQUIRED_ON_ADAPTER: () => NOT_REQUIRED_ON_ADAPTER,
42415
43102
  ObjectId: () => ObjectId,
42416
43103
  OdbcAdapter: () => OdbcAdapter,
43104
+ Point: () => Point,
42417
43105
  PostgresAdapter: () => PostgresAdapter,
42418
43106
  QueryBuilder: () => QueryBuilder,
42419
43107
  QueryCache: () => QueryCache,
@@ -42426,6 +43114,7 @@ __export(index_exports, {
42426
43114
  S3Storage: () => S3Storage,
42427
43115
  SQLTranslator: () => SQLTranslator,
42428
43116
  SQLiteAdapter: () => SQLiteAdapter,
43117
+ SpatialNotSupportedError: () => SpatialNotSupportedError,
42429
43118
  SqliteCollection: () => SqliteCollection,
42430
43119
  SqliteDatabase: () => SqliteDatabase,
42431
43120
  adapterColumns: () => adapterColumns,
@@ -42519,6 +43208,7 @@ var init_index = __esm({
42519
43208
  init_baseModel();
42520
43209
  init_queryBuilder();
42521
43210
  init_sqlTranslator();
43211
+ init_point();
42522
43212
  init_connectTimeout();
42523
43213
  init_cachedDatabase();
42524
43214
  init_fakeData2();
@@ -42542,6 +43232,7 @@ export {
42542
43232
  CachedDatabaseAdapter,
42543
43233
  Cursor,
42544
43234
  DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
43235
+ DEFAULT_SRID,
42545
43236
  Database,
42546
43237
  DatabaseResult,
42547
43238
  DatabaseUrl,
@@ -42557,6 +43248,7 @@ export {
42557
43248
  NOT_REQUIRED_ON_ADAPTER,
42558
43249
  ObjectId,
42559
43250
  OdbcAdapter,
43251
+ Point,
42560
43252
  PostgresAdapter,
42561
43253
  QueryBuilder,
42562
43254
  QueryCache,
@@ -42569,6 +43261,7 @@ export {
42569
43261
  S3Storage,
42570
43262
  SQLTranslator,
42571
43263
  SQLiteAdapter,
43264
+ SpatialNotSupportedError,
42572
43265
  SqliteCollection,
42573
43266
  SqliteDatabase,
42574
43267
  adapterColumns,