tina4-nodejs 3.13.101 → 3.13.104

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.
@@ -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
  *
@@ -2622,6 +2781,7 @@ __export(engine_exports, {
2622
2781
  Frond: () => Frond,
2623
2782
  MEMO_CACHE_MAX: () => MEMO_CACHE_MAX,
2624
2783
  TEMPLATE_CACHE_MAX: () => TEMPLATE_CACHE_MAX,
2784
+ expressionFormCache: () => expressionFormCache,
2625
2785
  filterChainCache: () => filterChainCache,
2626
2786
  pathParseCache: () => pathParseCache,
2627
2787
  setFormTokenSessionId: () => setFormTokenSessionId
@@ -3030,62 +3190,58 @@ function splitOutsideQuotes(expr, sep6) {
3030
3190
  parts.push(expr.slice(currentStart));
3031
3191
  return parts;
3032
3192
  }
3033
- function evalExpr(expr, context) {
3034
- expr = expr.trim();
3035
- if (expr.length >= 2) {
3036
- const q = expr[0];
3037
- if ((q === '"' || q === "'") && expr.endsWith(q) && !expr.slice(1, -1).includes(q)) {
3038
- return expr.slice(1, -1);
3039
- }
3040
- }
3041
- if (expr.length >= 2 && expr[0] === "(" && expr.endsWith(")")) {
3042
- let depth = 0;
3043
- let matched = true;
3044
- for (let pi = 0; pi < expr.length; pi++) {
3045
- if (expr[pi] === "(") depth++;
3046
- else if (expr[pi] === ")") depth--;
3047
- if (depth === 0 && pi < expr.length - 1) {
3048
- matched = false;
3049
- break;
3050
- }
3051
- }
3052
- if (matched) {
3053
- return evalExpr(expr.slice(1, -1), context);
3054
- }
3193
+ function parenthesizedInner(expr) {
3194
+ if (expr.length < 2 || expr[0] !== "(" || !expr.endsWith(")")) return null;
3195
+ let depth = 0;
3196
+ for (let index = 0; index < expr.length; index++) {
3197
+ if (expr[index] === "(") depth++;
3198
+ else if (expr[index] === ")") depth--;
3199
+ if (depth === 0 && index < expr.length - 1) return null;
3055
3200
  }
3056
- const ternaryIdx = findTernary(expr);
3057
- if (ternaryIdx !== -1) {
3058
- const condPart = expr.slice(0, ternaryIdx).trim();
3059
- const rest = expr.slice(ternaryIdx + 1);
3060
- const colonIdx = findColon(rest);
3061
- if (colonIdx !== -1) {
3062
- const truePart = rest.slice(0, colonIdx).trim();
3063
- const falsePart = rest.slice(colonIdx + 1).trim();
3064
- const cond = evalExpr(condPart, context);
3065
- return cond ? evalExpr(truePart, context) : evalExpr(falsePart, context);
3066
- }
3201
+ return expr.slice(1, -1);
3202
+ }
3203
+ function evalPrimary(expr, context) {
3204
+ const quote = expr[0];
3205
+ if (expr.length >= 2 && (quote === '"' || quote === "'") && expr.endsWith(quote) && !expr.slice(1, -1).includes(quote)) {
3206
+ return expr.slice(1, -1);
3067
3207
  }
3208
+ const inner = parenthesizedInner(expr);
3209
+ if (inner !== null) return evalExpr(inner, context);
3210
+ return EXPR_NOT_MATCHED;
3211
+ }
3212
+ function evalTernaryExpression(expr, context) {
3213
+ const ternaryIdx = findTernary(expr);
3214
+ if (ternaryIdx === -1) return EXPR_NOT_MATCHED;
3215
+ const rest = expr.slice(ternaryIdx + 1);
3216
+ const colonIdx = findColon(rest);
3217
+ if (colonIdx === -1) return EXPR_NOT_MATCHED;
3218
+ const condition = evalExpr(expr.slice(0, ternaryIdx).trim(), context);
3219
+ const branch = condition ? rest.slice(0, colonIdx) : rest.slice(colonIdx + 1);
3220
+ return evalExpr(branch.trim(), context);
3221
+ }
3222
+ function evalInlineIfExpression(expr, context) {
3068
3223
  const ifIdx = findOutsideQuotes(expr, " if ");
3069
- if (ifIdx >= 0) {
3070
- const elseIdx = findOutsideQuotes(expr, " else ");
3071
- if (elseIdx >= 0 && elseIdx > ifIdx) {
3072
- const valuePart = expr.slice(0, ifIdx).trim();
3073
- const condPart = expr.slice(ifIdx + 4, elseIdx).trim();
3074
- const elsePart = expr.slice(elseIdx + 6).trim();
3075
- const cond = evalExpr(condPart, context);
3076
- return cond ? evalExpr(valuePart, context) : evalExpr(elsePart, context);
3077
- }
3078
- }
3224
+ if (ifIdx < 0) return EXPR_NOT_MATCHED;
3225
+ const elseIdx = findOutsideQuotes(expr, " else ");
3226
+ if (elseIdx < 0 || elseIdx <= ifIdx) return EXPR_NOT_MATCHED;
3227
+ const condition = evalExpr(expr.slice(ifIdx + 4, elseIdx).trim(), context);
3228
+ const branch = condition ? expr.slice(0, ifIdx) : expr.slice(elseIdx + 6);
3229
+ return evalExpr(branch.trim(), context);
3230
+ }
3231
+ function evalCoalesceExpression(expr, context) {
3079
3232
  const qqIdx = findOutsideQuotes(expr, "??");
3080
- if (qqIdx !== -1) {
3081
- const left = expr.slice(0, qqIdx).trim();
3082
- const right = expr.slice(qqIdx + 2).trim();
3083
- const val = evalExpr(left, context);
3084
- if (val === null || val === void 0) {
3085
- return evalExpr(right, context);
3086
- }
3087
- return val;
3233
+ if (qqIdx === -1) return EXPR_NOT_MATCHED;
3234
+ const value = evalExpr(expr.slice(0, qqIdx).trim(), context);
3235
+ return value === null || value === void 0 ? evalExpr(expr.slice(qqIdx + 2).trim(), context) : value;
3236
+ }
3237
+ function evalConditional(expr, context) {
3238
+ for (const evaluator of [evalTernaryExpression, evalInlineIfExpression, evalCoalesceExpression]) {
3239
+ const result = evaluator(expr, context);
3240
+ if (result !== EXPR_NOT_MATCHED) return result;
3088
3241
  }
3242
+ return EXPR_NOT_MATCHED;
3243
+ }
3244
+ function evalConcatOrComparison(expr, context) {
3089
3245
  if (findOutsideQuotes(expr, "~") >= 0) {
3090
3246
  const parts = splitOutsideQuotes(expr, "~");
3091
3247
  if (parts.length > 1) {
@@ -3103,6 +3259,9 @@ function evalExpr(expr, context) {
3103
3259
  return evalComparison(expr, context);
3104
3260
  }
3105
3261
  }
3262
+ return EXPR_NOT_MATCHED;
3263
+ }
3264
+ function evalArithmeticExpression(expr, context) {
3106
3265
  for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
3107
3266
  const pos = findOutsideQuotes(expr, op);
3108
3267
  if (pos >= 0) {
@@ -3115,40 +3274,15 @@ function evalExpr(expr, context) {
3115
3274
  let rNum = rVal != null ? Number(rVal) : 0;
3116
3275
  if (isNaN(lNum)) lNum = 0;
3117
3276
  if (isNaN(rNum)) rNum = 0;
3118
- const opS = op.trim();
3119
- const bothInt = Number.isInteger(lNum) && Number.isInteger(rNum) && opS !== "/";
3120
- let result;
3121
- switch (opS) {
3122
- case "+":
3123
- result = lNum + rNum;
3124
- break;
3125
- case "-":
3126
- result = lNum - rNum;
3127
- break;
3128
- case "*":
3129
- result = lNum * rNum;
3130
- break;
3131
- case "//":
3132
- result = rNum !== 0 ? Math.floor(lNum / rNum) : 0;
3133
- break;
3134
- case "/":
3135
- result = rNum !== 0 ? lNum / rNum : 0;
3136
- break;
3137
- case "%":
3138
- result = rNum !== 0 ? lNum % rNum : 0;
3139
- break;
3140
- case "**":
3141
- result = lNum ** rNum;
3142
- break;
3143
- default:
3144
- result = 0;
3145
- }
3146
- return bothInt && Number.isInteger(result) ? result : result;
3277
+ return ARITHMETIC_OPERATIONS[op.trim()](lNum, rNum);
3147
3278
  } catch {
3148
3279
  return null;
3149
3280
  }
3150
3281
  }
3151
3282
  }
3283
+ return EXPR_NOT_MATCHED;
3284
+ }
3285
+ function evalFilterExpression(expr, context) {
3152
3286
  if (findOutsideQuotes(expr, "|") >= 0) {
3153
3287
  const [baseExpr, filters] = parseFilterChain(expr);
3154
3288
  if (filters.length > 0) {
@@ -3167,38 +3301,49 @@ function evalExpr(expr, context) {
3167
3301
  return value;
3168
3302
  }
3169
3303
  }
3170
- const fnMatch = expr.match(FN_CALL_RE);
3171
- if (fnMatch) {
3172
- const fnName = fnMatch[1];
3173
- const rawArgs = fnMatch[2] || "";
3174
- if (fnName.includes(".")) {
3175
- const lastDot = fnName.lastIndexOf(".");
3176
- const objPath = fnName.slice(0, lastDot);
3177
- const methodName = fnName.slice(lastDot + 1);
3178
- const obj = resolveVar(objPath, context);
3179
- if (obj && typeof obj === "object" && methodName in obj) {
3180
- const method = obj[methodName];
3181
- if (typeof method === "function") {
3182
- if (rawArgs.trim()) {
3183
- const parts = splitArgs(rawArgs);
3184
- const evalArgs = parts.map((a) => evalExpr(a.trim(), context));
3185
- return method.apply(obj, evalArgs);
3186
- }
3187
- return method.call(obj);
3188
- }
3189
- }
3190
- } else {
3191
- const fn = context[fnName] ?? resolveVar(fnName, context);
3192
- if (typeof fn === "function") {
3193
- if (rawArgs.trim()) {
3194
- const parts = splitArgs(rawArgs);
3195
- const evalArgs = parts.map((a) => evalExpr(a.trim(), context));
3196
- return fn(...evalArgs);
3197
- }
3198
- return fn();
3199
- }
3304
+ return EXPR_NOT_MATCHED;
3305
+ }
3306
+ function evaluateCallArgs(rawArgs, context) {
3307
+ return rawArgs.trim() ? splitArgs(rawArgs).map((arg) => evalExpr(arg.trim(), context)) : [];
3308
+ }
3309
+ function evalDottedFunction(name, rawArgs, context) {
3310
+ const lastDot = name.lastIndexOf(".");
3311
+ const owner = resolveVar(name.slice(0, lastDot), context);
3312
+ const member = name.slice(lastDot + 1);
3313
+ if (!owner || typeof owner !== "object" || !(member in owner)) {
3314
+ return EXPR_NOT_MATCHED;
3315
+ }
3316
+ const method = owner[member];
3317
+ return typeof method === "function" ? method.apply(owner, evaluateCallArgs(rawArgs, context)) : EXPR_NOT_MATCHED;
3318
+ }
3319
+ function evalFunctionExpression(expr, context) {
3320
+ const match = expr.match(FN_CALL_RE);
3321
+ if (!match) return EXPR_NOT_MATCHED;
3322
+ const name = match[1];
3323
+ const rawArgs = match[2] || "";
3324
+ if (name.includes(".")) return evalDottedFunction(name, rawArgs, context);
3325
+ const fn = context[name] ?? resolveVar(name, context);
3326
+ if (typeof fn === "function") return fn(...evaluateCallArgs(rawArgs, context));
3327
+ return EXPR_NOT_MATCHED;
3328
+ }
3329
+ function evalExpr(expr, context) {
3330
+ expr = expr.trim();
3331
+ const cachedForm = expressionFormCache.get(expr);
3332
+ if (cachedForm !== void 0) {
3333
+ if (cachedForm === -1) return resolveVar(expr, context);
3334
+ const result = EXPR_EVALUATORS[cachedForm](expr, context);
3335
+ return result === EXPR_NOT_MATCHED ? resolveVar(expr, context) : result;
3336
+ }
3337
+ for (let index = 0; index < EXPR_EVALUATORS.length; index++) {
3338
+ const result = EXPR_EVALUATORS[index](expr, context);
3339
+ if (result !== EXPR_NOT_MATCHED) {
3340
+ capCache(expressionFormCache, MEMO_CACHE_MAX);
3341
+ expressionFormCache.set(expr, index);
3342
+ return result;
3200
3343
  }
3201
3344
  }
3345
+ capCache(expressionFormCache, MEMO_CACHE_MAX);
3346
+ expressionFormCache.set(expr, FN_CALL_RE.test(expr) ? EXPR_EVALUATORS.length - 1 : -1);
3202
3347
  return resolveVar(expr, context);
3203
3348
  }
3204
3349
  function findTernary(expr) {
@@ -3654,7 +3799,7 @@ function _generateFormToken(descriptor = "") {
3654
3799
  function _generateFormTokenValue(descriptor = "") {
3655
3800
  return new SafeString(_buildFormTokenJwt(descriptor));
3656
3801
  }
3657
- var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
3802
+ var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
3658
3803
  var init_engine = __esm({
3659
3804
  "../frond/src/engine.ts"() {
3660
3805
  "use strict";
@@ -3756,6 +3901,25 @@ var init_engine = __esm({
3756
3901
  MEMO_CACHE_MAX = 1024;
3757
3902
  TOKEN_RE = /(\{%-?\s*[\s\S]*?\s*-?%\})|(\{\{-?\s*[\s\S]*?\s*-?\}\})|(\{#[\s\S]*?#\})/g;
3758
3903
  RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g;
3904
+ EXPR_NOT_MATCHED = Symbol("frond-expression-not-matched");
3905
+ ARITHMETIC_OPERATIONS = {
3906
+ "+": (left, right) => left + right,
3907
+ "-": (left, right) => left - right,
3908
+ "*": (left, right) => left * right,
3909
+ "//": (left, right) => right !== 0 ? Math.floor(left / right) : 0,
3910
+ "/": (left, right) => right !== 0 ? left / right : 0,
3911
+ "%": (left, right) => right !== 0 ? left % right : 0,
3912
+ "**": (left, right) => left ** right
3913
+ };
3914
+ EXPR_EVALUATORS = [
3915
+ evalPrimary,
3916
+ evalConditional,
3917
+ evalConcatOrComparison,
3918
+ evalArithmeticExpression,
3919
+ evalFilterExpression,
3920
+ evalFunctionExpression
3921
+ ];
3922
+ expressionFormCache = /* @__PURE__ */ new Map();
3759
3923
  VarRef = class {
3760
3924
  constructor(name) {
3761
3925
  this.name = name;
@@ -8768,6 +8932,12 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
8768
8932
  }
8769
8933
  }
8770
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
+ }
8771
8941
  const sessionToken = req2.session?.get?.("token");
8772
8942
  if (sessionToken && validToken(sessionToken)) {
8773
8943
  resolvedToken = sessionToken;
@@ -12232,7 +12402,7 @@ var init_metrics = __esm({
12232
12402
  };
12233
12403
  INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
12234
12404
  SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
12235
- FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_tests"];
12405
+ FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_referencing_test"];
12236
12406
  FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
12237
12407
  }
12238
12408
  });
@@ -23319,6 +23489,14 @@ function resolveSecuritySchemes() {
23319
23489
  const apiKeyIn = ["header", "query", "cookie"].includes(rawIn) ? rawIn : "header";
23320
23490
  schemes.apiKeyAuth = { type: "apiKey", name: apiKeyName, in: apiKeyIn };
23321
23491
  }
23492
+ const ssoIssuer = (process.env.TINA4_SSO_ISSUER ?? "").replace(/\/$/, "");
23493
+ if (ssoIssuer) {
23494
+ schemes.oidc = {
23495
+ type: "openIdConnect",
23496
+ openIdConnectUrl: `${ssoIssuer}/.well-known/openid-configuration`
23497
+ };
23498
+ schemes.ssoSession = { type: "apiKey", in: "cookie", name: "tina4_session" };
23499
+ }
23322
23500
  for (const [name, def] of Object.entries(registeredSchemes)) {
23323
23501
  schemes[name] = def;
23324
23502
  }
@@ -23500,7 +23678,9 @@ function generate(routes, models = []) {
23500
23678
  if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
23501
23679
  }
23502
23680
  } else if (routeRequiresAuth(route, method)) {
23503
- operation.security = sanitizeSecurity([{ [defaultScheme]: [] }], schemes);
23681
+ const requirements = [{ [defaultScheme]: [] }];
23682
+ if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
23683
+ operation.security = sanitizeSecurity(requirements, schemes);
23504
23684
  const responses = operation.responses;
23505
23685
  if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
23506
23686
  }
@@ -23814,6 +23994,298 @@ var init_src = __esm({
23814
23994
  }
23815
23995
  });
23816
23996
 
23997
+ // ../core/src/sso.ts
23998
+ var sso_exports = {};
23999
+ __export(sso_exports, {
24000
+ SSO: () => Sso,
24001
+ Sso: () => Sso,
24002
+ SsoError: () => SsoError
24003
+ });
24004
+ import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
24005
+ var SsoError, Sso;
24006
+ var init_sso = __esm({
24007
+ "../core/src/sso.ts"() {
24008
+ "use strict";
24009
+ SsoError = class extends Error {
24010
+ };
24011
+ Sso = class _Sso {
24012
+ static PENDING_KEY = "_tina4_sso_pending";
24013
+ static SESSION_KEY = "_tina4_sso";
24014
+ issuer;
24015
+ clientId;
24016
+ clientSecret;
24017
+ redirectUri;
24018
+ scopes;
24019
+ verify;
24020
+ postLogoutRedirectUri;
24021
+ claimMap;
24022
+ timeout;
24023
+ metadata = {};
24024
+ static mountedRouters = /* @__PURE__ */ new WeakSet();
24025
+ constructor(options = {}) {
24026
+ this.issuer = (options.issuer ?? process.env.TINA4_SSO_ISSUER ?? "").replace(/\/$/, "");
24027
+ this.clientId = options.clientId ?? process.env.TINA4_SSO_CLIENT_ID ?? "";
24028
+ this.clientSecret = options.clientSecret ?? process.env.TINA4_SSO_CLIENT_SECRET;
24029
+ this.redirectUri = options.redirectUri ?? process.env.TINA4_SSO_REDIRECT_URI ?? "";
24030
+ this.scopes = options.scopes ?? this.jsonEnv("TINA4_SSO_SCOPES", ["openid", "profile", "email"]);
24031
+ this.verify = options.verify ?? process.env.TINA4_SSO_VERIFY ?? "introspection";
24032
+ this.postLogoutRedirectUri = options.postLogoutRedirectUri ?? process.env.TINA4_SSO_POST_LOGOUT_REDIRECT_URI;
24033
+ this.claimMap = options.claimMap ?? this.jsonEnv("TINA4_SSO_CLAIM_MAP", {});
24034
+ this.timeout = options.timeout ?? 1e4;
24035
+ this.validateConfig();
24036
+ }
24037
+ static async fromIssuer(options = {}) {
24038
+ const value = new _Sso(options);
24039
+ await value.discover();
24040
+ return value;
24041
+ }
24042
+ static configured() {
24043
+ return ["TINA4_SSO_ISSUER", "TINA4_SSO_CLIENT_ID", "TINA4_SSO_REDIRECT_URI"].every((key) => Boolean(process.env[key]));
24044
+ }
24045
+ jsonEnv(name, fallback) {
24046
+ const raw = process.env[name];
24047
+ if (!raw) return fallback;
24048
+ try {
24049
+ return JSON.parse(raw);
24050
+ } catch {
24051
+ throw new SsoError(`${name} must be valid JSON`);
24052
+ }
24053
+ }
24054
+ static secureUrl(value, name) {
24055
+ let url;
24056
+ try {
24057
+ url = new URL(value);
24058
+ } catch {
24059
+ throw new SsoError(`${name} must be an absolute URL`);
24060
+ }
24061
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
24062
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
24063
+ throw new SsoError(`${name} must use HTTPS except on loopback`);
24064
+ }
24065
+ }
24066
+ validateConfig() {
24067
+ if (!this.issuer || !this.clientId || !this.redirectUri) {
24068
+ throw new SsoError("TINA4_SSO_ISSUER, TINA4_SSO_CLIENT_ID and TINA4_SSO_REDIRECT_URI are required");
24069
+ }
24070
+ _Sso.secureUrl(this.issuer, "issuer");
24071
+ _Sso.secureUrl(this.redirectUri, "redirect URI");
24072
+ if (!["introspection", "jwks"].includes(this.verify)) throw new SsoError("TINA4_SSO_VERIFY must be introspection or jwks");
24073
+ if (this.verify === "jwks") throw new SsoError("jwks verification requires an installed cryptography capability");
24074
+ if (this.verify === "introspection" && !this.clientSecret) throw new SsoError("introspection verification requires TINA4_SSO_CLIENT_SECRET");
24075
+ if (!Array.isArray(this.scopes) || !this.scopes.includes("openid")) throw new SsoError("TINA4_SSO_SCOPES must be a list containing openid");
24076
+ }
24077
+ async requestJson(url, form, bearer, basic = false) {
24078
+ const headers = { Accept: "application/json" };
24079
+ let body;
24080
+ if (form) {
24081
+ const parameters = new URLSearchParams();
24082
+ for (const [key, value] of Object.entries(form)) parameters.set(key, String(value));
24083
+ body = parameters.toString();
24084
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
24085
+ }
24086
+ if (bearer) headers.Authorization = `Bearer ${bearer}`;
24087
+ if (basic) headers.Authorization = `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64")}`;
24088
+ const controller = new AbortController();
24089
+ const timer = setTimeout(() => controller.abort(), this.timeout);
24090
+ try {
24091
+ const response = await fetch(url, { method: form ? "POST" : "GET", headers, body, signal: controller.signal });
24092
+ if (!response.ok) throw new SsoError("OIDC provider request failed");
24093
+ const result = await response.json();
24094
+ if (!result || typeof result !== "object" || Array.isArray(result)) throw new SsoError("OIDC provider returned a non-object response");
24095
+ return result;
24096
+ } catch (error) {
24097
+ if (error instanceof SsoError) throw error;
24098
+ throw new SsoError("OIDC provider request failed");
24099
+ } finally {
24100
+ clearTimeout(timer);
24101
+ }
24102
+ }
24103
+ async discover(force = false) {
24104
+ if (Object.keys(this.metadata).length && !force) return { ...this.metadata };
24105
+ const result = await this.requestJson(`${this.issuer}/.well-known/openid-configuration`);
24106
+ if (result.issuer !== this.issuer) throw new SsoError("OIDC discovery issuer does not exactly match configuration");
24107
+ const required = ["authorization_endpoint", "token_endpoint"];
24108
+ if (this.verify === "introspection") required.push("introspection_endpoint");
24109
+ for (const key of required) {
24110
+ if (!result[key]) throw new SsoError(`OIDC discovery is missing ${key}`);
24111
+ _Sso.secureUrl(result[key], key);
24112
+ }
24113
+ this.metadata = result;
24114
+ return { ...result };
24115
+ }
24116
+ static safeReturn(value) {
24117
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\\")) return "/";
24118
+ return [...value].some((char) => char.charCodeAt(0) < 32) ? "/" : value;
24119
+ }
24120
+ session(value) {
24121
+ return value?.session ?? value;
24122
+ }
24123
+ async login(requestOrSession, returnTo = "/") {
24124
+ const session = this.session(requestOrSession);
24125
+ if (!session) throw new SsoError("SSO login requires a Tina4 Session");
24126
+ const state = randomBytes5(32).toString("base64url");
24127
+ const nonce = randomBytes5(32).toString("base64url");
24128
+ const verifier = randomBytes5(64).toString("base64url");
24129
+ const challenge = createHash9("sha256").update(verifier).digest("base64url");
24130
+ session.set(_Sso.PENDING_KEY, { state, nonce, verifier, return_to: _Sso.safeReturn(returnTo), created_at: Math.floor(Date.now() / 1e3) });
24131
+ const metadata = await this.discover();
24132
+ const query = new URLSearchParams({
24133
+ client_id: this.clientId,
24134
+ redirect_uri: this.redirectUri,
24135
+ response_type: "code",
24136
+ scope: this.scopes.join(" "),
24137
+ state,
24138
+ nonce,
24139
+ code_challenge: challenge,
24140
+ code_challenge_method: "S256"
24141
+ });
24142
+ return `${metadata.authorization_endpoint}?${query}`;
24143
+ }
24144
+ static equal(left, right) {
24145
+ const a = Buffer.from(String(left ?? ""));
24146
+ const b = Buffer.from(String(right ?? ""));
24147
+ return a.length === b.length && timingSafeEqual3(a, b);
24148
+ }
24149
+ static jwtPayload(token) {
24150
+ try {
24151
+ return JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
24152
+ } catch {
24153
+ throw new SsoError("provider returned an invalid ID token");
24154
+ }
24155
+ }
24156
+ async introspect(accessToken) {
24157
+ const metadata = await this.discover();
24158
+ const result = await this.requestJson(metadata.introspection_endpoint, { token: accessToken, token_type_hint: "access_token" }, void 0, true);
24159
+ if (result.active !== true || result.iss !== this.issuer) throw new SsoError("OIDC access token is inactive or has the wrong issuer");
24160
+ const audience = result.aud ?? result.client_id;
24161
+ const valid = (Array.isArray(audience) ? audience.includes(this.clientId) : audience === this.clientId) || result.client_id === this.clientId;
24162
+ if (!valid) throw new SsoError("OIDC token audience mismatch");
24163
+ return result;
24164
+ }
24165
+ claim(claims, configured, fallback) {
24166
+ let value = claims;
24167
+ for (const part of (configured ?? fallback).split(".")) value = value && typeof value === "object" ? value[part] : void 0;
24168
+ return value;
24169
+ }
24170
+ normalize(claims) {
24171
+ const subject = this.claim(claims, this.claimMap.subject, "sub");
24172
+ const issuer = this.claim(claims, this.claimMap.issuer, "iss") ?? this.issuer;
24173
+ if (!subject || issuer !== this.issuer) throw new SsoError("OIDC identity is missing a valid issuer or subject");
24174
+ const roles = [...this.claim(claims, this.claimMap.roles, "realm_access.roles") ?? [], ...claims.resource_access?.[this.clientId]?.roles ?? []];
24175
+ const groups = this.claim(claims, this.claimMap.groups, "groups") ?? [];
24176
+ return {
24177
+ issuer,
24178
+ subject,
24179
+ username: this.claim(claims, this.claimMap.username, "preferred_username") ?? null,
24180
+ email: this.claim(claims, this.claimMap.email, "email") ?? null,
24181
+ name: this.claim(claims, this.claimMap.name, "name") ?? null,
24182
+ roles: [...new Set(roles.map(String))].sort(),
24183
+ groups: [...new Set(groups.map(String))].sort()
24184
+ };
24185
+ }
24186
+ async callback(requestOrSession, query) {
24187
+ const session = this.session(requestOrSession);
24188
+ const values = query ?? requestOrSession?.query ?? {};
24189
+ const pending = session?.get(_Sso.PENDING_KEY);
24190
+ session?.delete(_Sso.PENDING_KEY);
24191
+ if (!pending || !values.code || !_Sso.equal(values.state, pending.state)) throw new SsoError("OIDC callback state is invalid or already consumed");
24192
+ if (Math.floor(Date.now() / 1e3) - Number(pending.created_at ?? 0) > 600) throw new SsoError("OIDC callback state has expired");
24193
+ const metadata = await this.discover();
24194
+ const tokens = await this.requestJson(metadata.token_endpoint, {
24195
+ grant_type: "authorization_code",
24196
+ code: values.code,
24197
+ redirect_uri: this.redirectUri,
24198
+ client_id: this.clientId,
24199
+ code_verifier: pending.verifier
24200
+ }, void 0, Boolean(this.clientSecret));
24201
+ if (!tokens.access_token || !tokens.id_token) throw new SsoError("OIDC token response is incomplete");
24202
+ if (this.verify === "jwks") throw new SsoError("JWKS verification requires an installed cryptography capability");
24203
+ const claims = await this.introspect(tokens.access_token);
24204
+ if (!_Sso.equal(_Sso.jwtPayload(tokens.id_token).nonce, pending.nonce)) throw new SsoError("OIDC ID token nonce mismatch");
24205
+ if (metadata.userinfo_endpoint) Object.assign(claims, await this.requestJson(metadata.userinfo_endpoint, void 0, tokens.access_token));
24206
+ const identity = this.normalize(claims);
24207
+ session.regenerate();
24208
+ session.set(_Sso.SESSION_KEY, {
24209
+ version: 1,
24210
+ identity,
24211
+ access_token: tokens.access_token,
24212
+ refresh_token: tokens.refresh_token,
24213
+ id_token: tokens.id_token,
24214
+ expires_at: Math.floor(Date.now() / 1e3) + Number(tokens.expires_in ?? 0)
24215
+ });
24216
+ return { identity, return_to: _Sso.safeReturn(pending.return_to) };
24217
+ }
24218
+ identity(requestOrSession) {
24219
+ const stored = this.session(requestOrSession)?.get(_Sso.SESSION_KEY);
24220
+ const identity = stored?.identity ?? null;
24221
+ if (identity && requestOrSession?.session) requestOrSession.user = identity;
24222
+ return identity;
24223
+ }
24224
+ async refresh(requestOrSession) {
24225
+ const session = this.session(requestOrSession);
24226
+ const stored = session?.get(_Sso.SESSION_KEY);
24227
+ if (!stored?.refresh_token) {
24228
+ session?.delete(_Sso.SESSION_KEY);
24229
+ throw new SsoError("OIDC session cannot be refreshed");
24230
+ }
24231
+ try {
24232
+ const metadata = await this.discover();
24233
+ const tokens = await this.requestJson(metadata.token_endpoint, {
24234
+ grant_type: "refresh_token",
24235
+ refresh_token: stored.refresh_token,
24236
+ client_id: this.clientId
24237
+ }, void 0, Boolean(this.clientSecret));
24238
+ const claims = await this.introspect(tokens.access_token);
24239
+ if (metadata.userinfo_endpoint) Object.assign(claims, await this.requestJson(metadata.userinfo_endpoint, void 0, tokens.access_token));
24240
+ const identity = this.normalize(claims);
24241
+ session.set(_Sso.SESSION_KEY, {
24242
+ ...stored,
24243
+ identity,
24244
+ access_token: tokens.access_token,
24245
+ refresh_token: tokens.refresh_token ?? stored.refresh_token,
24246
+ id_token: tokens.id_token ?? stored.id_token,
24247
+ expires_at: Math.floor(Date.now() / 1e3) + Number(tokens.expires_in ?? 0)
24248
+ });
24249
+ return identity;
24250
+ } catch (error) {
24251
+ session?.delete(_Sso.SESSION_KEY);
24252
+ throw error;
24253
+ }
24254
+ }
24255
+ async logout(requestOrSession, returnTo = "/") {
24256
+ const session = this.session(requestOrSession);
24257
+ const stored = session?.get(_Sso.SESSION_KEY);
24258
+ session?.destroy();
24259
+ const endpoint = (await this.discover()).end_session_endpoint;
24260
+ const target = this.postLogoutRedirectUri ?? _Sso.safeReturn(returnTo);
24261
+ if (!endpoint) return target;
24262
+ const params = new URLSearchParams({ post_logout_redirect_uri: target, client_id: this.clientId });
24263
+ if (stored?.id_token) params.set("id_token_hint", stored.id_token);
24264
+ return `${endpoint}?${params}`;
24265
+ }
24266
+ static async mountConfigured(router) {
24267
+ if (_Sso.mountedRouters.has(router) || !_Sso.configured()) return false;
24268
+ const owned = /* @__PURE__ */ new Set(["GET /auth/login", "GET /auth/callback", "POST /auth/logout"]);
24269
+ const collisions = router.getRoutes().map((route) => `${route.method} ${route.pattern}`).filter((route) => owned.has(route));
24270
+ if (collisions.length) throw new SsoError(`SSO route collision: ${collisions.join(", ")}`);
24271
+ const sso = await _Sso.fromIssuer();
24272
+ router.get("/auth/login", async (req2, res) => res.redirect(await sso.login(req2, req2.query?.return_to ?? "/")));
24273
+ router.get("/auth/callback", async (req2, res) => {
24274
+ try {
24275
+ return res.redirect((await sso.callback(req2)).return_to);
24276
+ } catch (error) {
24277
+ const message = error instanceof SsoError ? error.message : "OIDC callback failed";
24278
+ return res.error("SSO_CALLBACK_FAILED", message, 400);
24279
+ }
24280
+ });
24281
+ router.post("/auth/logout", async (req2, res) => res.redirect(await sso.logout(req2, req2.query?.return_to ?? "/")));
24282
+ _Sso.mountedRouters.add(router);
24283
+ return true;
24284
+ }
24285
+ };
24286
+ }
24287
+ });
24288
+
23817
24289
  // ../core/src/docsAutoDiscovery.ts
23818
24290
  var docsAutoDiscovery_exports = {};
23819
24291
  __export(docsAutoDiscovery_exports, {
@@ -23883,7 +24355,7 @@ var init_docsAutoDiscovery = __esm({
23883
24355
 
23884
24356
  // ../core/src/server.ts
23885
24357
  import { createServer as createServer2 } from "node:http";
23886
- import { randomBytes as randomBytes5 } from "node:crypto";
24358
+ import { randomBytes as randomBytes6 } from "node:crypto";
23887
24359
  import { resolve as resolve14, dirname as dirname11, join as join24, relative as relative8 } from "node:path";
23888
24360
  import { existsSync as existsSync22, readdirSync as readdirSync14, readFileSync as readFileSync21, statSync as statSync15 } from "node:fs";
23889
24361
  import { isatty } from "node:tty";
@@ -24523,7 +24995,7 @@ async function renderDispatchError(err, req2, res, templatesDir) {
24523
24995
  }
24524
24996
  }
24525
24997
  }
24526
- const requestId = Log.getRequestId() ?? randomBytes5(4).toString("hex");
24998
+ const requestId = Log.getRequestId() ?? randomBytes6(4).toString("hex");
24527
24999
  if (wantsJson(req2)) {
24528
25000
  const body = negotiatedErrorBody(500, "Internal Server Error", requestId);
24529
25001
  res.raw.writeHead(500, { "Content-Type": "application/json" });
@@ -24594,7 +25066,7 @@ function serveStaticAsset(ctx) {
24594
25066
  return false;
24595
25067
  }
24596
25068
  async function serveNotFound(ctx) {
24597
- const requestId = Log.getRequestId() ?? randomBytes5(4).toString("hex");
25069
+ const requestId = Log.getRequestId() ?? randomBytes6(4).toString("hex");
24598
25070
  if (wantsJson(ctx.req)) {
24599
25071
  const body = negotiatedErrorBody(404, "Not Found", requestId);
24600
25072
  ctx.res.raw.writeHead(404, httpReason(404), { "Content-Type": "application/json" });
@@ -24714,7 +25186,7 @@ async function dispatchInner(ctx, rawReq, rawRes, requestId) {
24714
25186
  }
24715
25187
  }
24716
25188
  async function runDispatch(ctx, rawReq, rawRes) {
24717
- const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes5(4).toString("hex");
25189
+ const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes6(4).toString("hex");
24718
25190
  if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
24719
25191
  return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
24720
25192
  }
@@ -24848,6 +25320,8 @@ ${reset2}
24848
25320
  console.log(`
24849
25321
  No routes directory found at ${routesDir}`);
24850
25322
  }
25323
+ const { Sso: Sso2 } = await Promise.resolve().then(() => (init_sso(), sso_exports));
25324
+ await Sso2.mountConfigured(router);
24851
25325
  if (attachCsrfFromEnv()) {
24852
25326
  console.log(`
24853
25327
  \x1B[36mCSRF\x1B[0m protection enabled (TINA4_CSRF)`);
@@ -25840,7 +26314,7 @@ var init_mqttMessage = __esm({
25840
26314
  // ../core/src/mqtt.ts
25841
26315
  import net2 from "node:net";
25842
26316
  import tls from "node:tls";
25843
- import { randomBytes as randomBytes6 } from "node:crypto";
26317
+ import { randomBytes as randomBytes7 } from "node:crypto";
25844
26318
  import { existsSync as existsSync24, readFileSync as readFileSync22 } from "node:fs";
25845
26319
  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;
25846
26320
  var init_mqtt = __esm({
@@ -25928,7 +26402,7 @@ var init_mqtt = __esm({
25928
26402
  this.caFile = options.caFile ?? (Env.str("TINA4_MQTT_CA_FILE") || null);
25929
26403
  this.tlsVerify = options.tlsVerify ?? Env.bool("TINA4_MQTT_TLS_VERIFY", true);
25930
26404
  let cid = options.clientId ?? (Env.str("TINA4_MQTT_CLIENT_ID") || null);
25931
- if (cid === null || cid === "") cid = "tina4-" + randomBytes6(8).toString("hex");
26405
+ if (cid === null || cid === "") cid = "tina4-" + randomBytes7(8).toString("hex");
25932
26406
  this.clientId = cid;
25933
26407
  this.keepalive = options.keepalive ?? Env.int("TINA4_MQTT_KEEPALIVE", DEFAULT_KEEPALIVE);
25934
26408
  this.cleanSession = options.cleanSession ?? true;
@@ -26858,7 +27332,7 @@ var init_service = __esm({
26858
27332
  import http from "node:http";
26859
27333
  import https from "node:https";
26860
27334
  import { URL as URL2 } from "node:url";
26861
- import { randomBytes as randomBytes7 } from "node:crypto";
27335
+ import { randomBytes as randomBytes8 } from "node:crypto";
26862
27336
  import { promises as fsp, createWriteStream } from "node:fs";
26863
27337
  import { basename as basename5 } from "node:path";
26864
27338
  import { pipeline } from "node:stream/promises";
@@ -27156,7 +27630,7 @@ var init_api = __esm({
27156
27630
  return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
27157
27631
  }
27158
27632
  const partContentType = guessContentType(uploadName);
27159
- const boundary = "----Tina4Boundary" + randomBytes7(16).toString("hex");
27633
+ const boundary = "----Tina4Boundary" + randomBytes8(16).toString("hex");
27160
27634
  const bodyBuffer = buildMultipartBody(boundary, fieldName, uploadName, content, partContentType, extraFields);
27161
27635
  const contentType = `multipart/form-data; boundary=${boundary}`;
27162
27636
  return this.execute("POST", this.buildUrl(path8), bodyBuffer, contentType, headers);
@@ -31988,10 +32462,13 @@ __export(src_exports2, {
31988
32462
  RouteGroup: () => RouteGroup,
31989
32463
  RouteRef: () => RouteRef,
31990
32464
  Router: () => Router,
32465
+ SSO: () => Sso,
31991
32466
  SafeString: () => SafeString2,
31992
32467
  SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
31993
32468
  ServiceRunner: () => ServiceRunner,
31994
32469
  Session: () => Session,
32470
+ Sso: () => Sso,
32471
+ SsoError: () => SsoError,
31995
32472
  TAKEOVER_KILLED: () => TAKEOVER_KILLED,
31996
32473
  TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
31997
32474
  TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
@@ -32228,6 +32705,7 @@ var init_src2 = __esm({
32228
32705
  init_htmlElement();
32229
32706
  init_errorOverlay();
32230
32707
  init_ai();
32708
+ init_sso();
32231
32709
  init_aiClient();
32232
32710
  init_liteBackend();
32233
32711
  init_rabbitmqBackend();
@@ -33326,6 +33804,8 @@ function fieldTypeToPostgres(def) {
33326
33804
  return "TEXT";
33327
33805
  case "json":
33328
33806
  return "JSONB";
33807
+ case "point":
33808
+ return SQLTranslator.pointColumnType("postgres", def.srid ?? 4326);
33329
33809
  case "string":
33330
33810
  return def.maxLength ? `VARCHAR(${def.maxLength})` : "VARCHAR(255)";
33331
33811
  default:
@@ -38949,10 +39429,13 @@ var init_queryBuilder = __esm({
38949
39429
  "use strict";
38950
39430
  init_database();
38951
39431
  init_databaseResult();
39432
+ init_point();
39433
+ init_sqlTranslator();
38952
39434
  QueryBuilder = class _QueryBuilder {
38953
39435
  table;
38954
39436
  db;
38955
39437
  columns = ["*"];
39438
+ selectParams = [];
38956
39439
  wheres = [];
38957
39440
  params = [];
38958
39441
  joinClauses = [];
@@ -38960,14 +39443,17 @@ var init_queryBuilder = __esm({
38960
39443
  havings = [];
38961
39444
  havingParams = [];
38962
39445
  orderByCols = [];
39446
+ orderByParams = [];
39447
+ primaryKey;
38963
39448
  limitVal;
38964
39449
  offsetVal;
38965
39450
  /**
38966
39451
  * Private constructor — use static factory methods.
38967
39452
  */
38968
- constructor(table2, db) {
39453
+ constructor(table2, db, primaryKey) {
38969
39454
  this.table = table2;
38970
39455
  this.db = db;
39456
+ this.primaryKey = primaryKey;
38971
39457
  }
38972
39458
  /**
38973
39459
  * Create a QueryBuilder for a table.
@@ -38976,8 +39462,8 @@ var init_queryBuilder = __esm({
38976
39462
  * @param db - Optional database adapter.
38977
39463
  * @returns A new QueryBuilder instance.
38978
39464
  */
38979
- static fromTable(tableName, db) {
38980
- return new _QueryBuilder(tableName, db);
39465
+ static fromTable(tableName, db, primaryKey) {
39466
+ return new _QueryBuilder(tableName, db, primaryKey);
38981
39467
  }
38982
39468
  /**
38983
39469
  * Set the columns to select.
@@ -38988,6 +39474,7 @@ var init_queryBuilder = __esm({
38988
39474
  select(...cols) {
38989
39475
  if (cols.length > 0) {
38990
39476
  this.columns = cols;
39477
+ this.selectParams = [];
38991
39478
  }
38992
39479
  return this;
38993
39480
  }
@@ -39069,6 +39556,41 @@ var init_queryBuilder = __esm({
39069
39556
  this.orderByCols.push(expression);
39070
39557
  return this;
39071
39558
  }
39559
+ withinDistance(column2, pointValue, radiusMetres, srid = DEFAULT_SRID) {
39560
+ const radius = Number(radiusMetres);
39561
+ if (!Number.isFinite(radius) || radius < 0) throw new RangeError("Spatial radius must be finite and greater than or equal to zero");
39562
+ const point = Point.parse(pointValue, srid);
39563
+ return this.where(SQLTranslator.withinDistance(this.engine(), column2, point.srid), [point.lon, point.lat, radius]);
39564
+ }
39565
+ intersects(column2, geometry, srid = DEFAULT_SRID) {
39566
+ const [bound, form] = Point.geometryBinding(geometry, srid);
39567
+ return this.where(SQLTranslator.intersects(this.engine(), column2, form, srid), [bound]);
39568
+ }
39569
+ bbox(column2, minLon, minLat, maxLon, maxLat, srid = DEFAULT_SRID) {
39570
+ const values = [minLon, minLat, maxLon, maxLat].map(Number);
39571
+ if (!values.every(Number.isFinite)) throw new TypeError("Bounding-box coordinates must be finite numbers");
39572
+ const [west, south, east, north] = values;
39573
+ new Point(west, south, srid);
39574
+ new Point(east, north, srid);
39575
+ if (west > east || south > north) throw new RangeError("Bounding box must be ordered west, south, east, north");
39576
+ return this.where(SQLTranslator.bbox(this.engine(), column2, srid), values);
39577
+ }
39578
+ selectDistance(column2, pointValue, alias = "distance", srid = DEFAULT_SRID) {
39579
+ const point = Point.parse(pointValue, srid);
39580
+ this.columns.push(SQLTranslator.distanceAs(this.engine(), column2, alias, point.srid));
39581
+ this.selectParams.push(point.lon, point.lat);
39582
+ return this;
39583
+ }
39584
+ orderByDistance(column2, pointValue, direction = "ASC", srid = DEFAULT_SRID) {
39585
+ const order = direction.toUpperCase();
39586
+ if (order !== "ASC" && order !== "DESC") throw new TypeError("Distance order direction must be ASC or DESC");
39587
+ if (!this.primaryKey) throw new Error("Stable spatial ordering needs a primary key; use BaseModel.query() or pass one to fromTable()");
39588
+ const point = Point.parse(pointValue, srid);
39589
+ this.orderByCols.push(`${SQLTranslator.distance(this.engine(), column2, point.srid)} ${order}`);
39590
+ this.orderByParams.push(point.lon, point.lat);
39591
+ this.orderByCols.push(`${SQLTranslator.spatialIdentifier(this.primaryKey, "primary key")} ASC`);
39592
+ return this;
39593
+ }
39072
39594
  /**
39073
39595
  * Set LIMIT and optional OFFSET.
39074
39596
  *
@@ -39134,7 +39656,7 @@ var init_queryBuilder = __esm({
39134
39656
  async get() {
39135
39657
  this.ensureDb();
39136
39658
  const sql = this.toSql();
39137
- const allParams = [...this.params, ...this.havingParams];
39659
+ const allParams = [...this.selectParams, ...this.params, ...this.havingParams, ...this.orderByParams];
39138
39660
  const queryParams = allParams.length > 0 ? allParams : void 0;
39139
39661
  const rows = await adapterFetch(
39140
39662
  this.db,
@@ -39162,7 +39684,7 @@ var init_queryBuilder = __esm({
39162
39684
  async first() {
39163
39685
  this.ensureDb();
39164
39686
  const sql = this.toSql();
39165
- const allParams = [...this.params, ...this.havingParams];
39687
+ const allParams = [...this.selectParams, ...this.params, ...this.havingParams, ...this.orderByParams];
39166
39688
  return adapterFetchOne(
39167
39689
  this.db,
39168
39690
  sql,
@@ -39177,9 +39699,18 @@ var init_queryBuilder = __esm({
39177
39699
  async count() {
39178
39700
  this.ensureDb();
39179
39701
  const original = this.columns;
39702
+ const originalSelectParams = this.selectParams;
39703
+ const originalOrder = this.orderByCols;
39704
+ const originalOrderParams = this.orderByParams;
39180
39705
  this.columns = ["COUNT(*) as cnt"];
39706
+ this.selectParams = [];
39707
+ this.orderByCols = [];
39708
+ this.orderByParams = [];
39181
39709
  const sql = this.toSql();
39182
39710
  this.columns = original;
39711
+ this.selectParams = originalSelectParams;
39712
+ this.orderByCols = originalOrder;
39713
+ this.orderByParams = originalOrderParams;
39183
39714
  const allParams = [...this.params, ...this.havingParams];
39184
39715
  const row = await adapterFetchOne(
39185
39716
  this.db,
@@ -39364,6 +39895,10 @@ var init_queryBuilder = __esm({
39364
39895
  }
39365
39896
  }
39366
39897
  }
39898
+ engine() {
39899
+ this.ensureDb();
39900
+ return this.db.getDatabaseType();
39901
+ }
39367
39902
  };
39368
39903
  }
39369
39904
  });
@@ -39387,6 +39922,11 @@ function toDbFieldValue(def, value) {
39387
39922
  if (def?.type === "json" && value !== null && value !== void 0 && typeof value !== "string") {
39388
39923
  return JSON.stringify(value);
39389
39924
  }
39925
+ if (def?.type === "point" && value !== null && value !== void 0) {
39926
+ const point = Point.parse(value, def.srid ?? DEFAULT_SRID);
39927
+ if (point.srid !== (def.srid ?? DEFAULT_SRID)) throw new TypeError(`Point field expects SRID ${def.srid ?? DEFAULT_SRID}; received ${point.srid}`);
39928
+ return point.ewkt;
39929
+ }
39390
39930
  return value;
39391
39931
  }
39392
39932
  function fromDbFieldValue(def, value) {
@@ -39397,6 +39937,11 @@ function fromDbFieldValue(def, value) {
39397
39937
  return value;
39398
39938
  }
39399
39939
  }
39940
+ if (def?.type === "point" && value !== null && value !== void 0) {
39941
+ const point = Point.parse(value, def.srid ?? DEFAULT_SRID);
39942
+ if (point.srid !== (def.srid ?? DEFAULT_SRID)) throw new TypeError(`Point field expects SRID ${def.srid ?? DEFAULT_SRID}; received ${point.srid}`);
39943
+ return point;
39944
+ }
39400
39945
  return value;
39401
39946
  }
39402
39947
  function _pluralRelKeys() {
@@ -39432,6 +39977,7 @@ var init_baseModel = __esm({
39432
39977
  init_sqlite();
39433
39978
  init_sqlTranslator();
39434
39979
  init_src2();
39980
+ init_point();
39435
39981
  _fkRegistry = /* @__PURE__ */ new Map();
39436
39982
  EAGER_IN_CHUNK = 500;
39437
39983
  modelQueryCache = new QueryCache({ defaultTtl: 0, maxSize: 500 });
@@ -39487,7 +40033,9 @@ var init_baseModel = __esm({
39487
40033
  for (const [name, def] of Object.entries(fields0)) {
39488
40034
  if (def.default === void 0) continue;
39489
40035
  let dv = typeof def.default === "function" ? def.default() : def.default;
39490
- if (dv !== null && typeof dv === "object") dv = structuredClone(dv);
40036
+ if (def.type === "point" && dv !== null && dv !== void 0) {
40037
+ dv = fromDbFieldValue(def, dv);
40038
+ } else if (dv !== null && typeof dv === "object") dv = structuredClone(dv);
39491
40039
  this[name] = dv;
39492
40040
  }
39493
40041
  if (data) {
@@ -39589,7 +40137,7 @@ var init_baseModel = __esm({
39589
40137
  * @returns A QueryBuilder instance bound to this model's table and database.
39590
40138
  */
39591
40139
  static query() {
39592
- return QueryBuilder.fromTable(this.tableName, this.getDb());
40140
+ return QueryBuilder.fromTable(this.tableName, this.getDb(), this.getPkColumn());
39593
40141
  }
39594
40142
  /**
39595
40143
  * Get the database adapter for this model.
@@ -40042,7 +40590,7 @@ var init_baseModel = __esm({
40042
40590
  for (const key of Object.keys(ModelClass.fields)) {
40043
40591
  if (this[key] !== void 0) {
40044
40592
  const outKey = case_ === "snake" ? ModelClass.fieldMapping[key] ?? key : key;
40045
- result[outKey] = this[key];
40593
+ result[outKey] = this[key] instanceof Point ? this[key].geojson : this[key];
40046
40594
  }
40047
40595
  }
40048
40596
  if (ModelClass.softDelete && this.is_deleted !== void 0) {
@@ -40102,6 +40650,19 @@ var init_baseModel = __esm({
40102
40650
  }
40103
40651
  return result;
40104
40652
  }
40653
+ toFeature(geometryField, include) {
40654
+ const ModelClass = this.constructor;
40655
+ const pointFields = Object.entries(ModelClass.fields).filter(([, def]) => def.type === "point").map(([name]) => name);
40656
+ const field = geometryField ?? pointFields[0];
40657
+ if (!field || !pointFields.includes(field)) throw new Error("toFeature() needs a declared point field");
40658
+ const properties = this.toDict(include, "camel");
40659
+ const geometry = properties[field] ?? null;
40660
+ delete properties[field];
40661
+ return { type: "Feature", geometry, properties };
40662
+ }
40663
+ static featureCollection(models, geometryField, include) {
40664
+ return { type: "FeatureCollection", features: models.map((model) => model.toFeature(geometryField, include)) };
40665
+ }
40105
40666
  /**
40106
40667
  * Convert to an associative object (alias for toDict).
40107
40668
  */
@@ -40152,7 +40713,10 @@ var init_baseModel = __esm({
40152
40713
  */
40153
40714
  static async createTable() {
40154
40715
  const db = this.getDb();
40155
- if (await adapterTableExists(db, this.tableName)) return true;
40716
+ const pointFields = Object.entries(this.fields).filter(([, def]) => def.type === "point");
40717
+ const engine = db.getDatabaseType();
40718
+ if (pointFields.length > 0) SQLTranslator.requireSpatial(engine, "PointField");
40719
+ if (await adapterTableExists(db, this.tableName)) return this.createSpatialIndexes(db, pointFields);
40156
40720
  if (typeof db.createTable === "function" || typeof db.createTableAsync === "function") {
40157
40721
  const mappedFields = {};
40158
40722
  for (const [fieldName, def] of Object.entries(this.fields)) {
@@ -40168,7 +40732,7 @@ var init_baseModel = __esm({
40168
40732
  mappedFields["is_deleted"] = { type: "integer", default: 0 };
40169
40733
  }
40170
40734
  await adapterCreateTable(db, this.tableName, mappedFields);
40171
- return true;
40735
+ return this.createSpatialIndexes(db, pointFields);
40172
40736
  }
40173
40737
  const typeMap = {
40174
40738
  integer: "INTEGER",
@@ -40215,6 +40779,14 @@ var init_baseModel = __esm({
40215
40779
  }
40216
40780
  return true;
40217
40781
  }
40782
+ static async createSpatialIndexes(db, fields) {
40783
+ for (const [fieldName, def] of fields) {
40784
+ SQLTranslator.pointColumnType(db.getDatabaseType(), def.srid ?? DEFAULT_SRID);
40785
+ if (def.spatialIndex === false) continue;
40786
+ await adapterExecute(db, SQLTranslator.spatialIndex(db.getDatabaseType(), this.tableName, this.getDbColumn(fieldName)));
40787
+ }
40788
+ return true;
40789
+ }
40218
40790
  /**
40219
40791
  * Find a record by primary key or throw an error if not found.
40220
40792
  */
@@ -41154,7 +41726,7 @@ var init_seeder = __esm({
41154
41726
 
41155
41727
  // src/docstore.ts
41156
41728
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
41157
- import { randomBytes as randomBytes8 } from "node:crypto";
41729
+ import { randomBytes as randomBytes9 } from "node:crypto";
41158
41730
  import { mkdirSync as mkdirSync19 } from "node:fs";
41159
41731
  import { dirname as dirname14, isAbsolute as isAbsolute6, join as join31 } from "node:path";
41160
41732
  function iso(d) {
@@ -41506,8 +42078,8 @@ var init_docstore = __esm({
41506
42078
  OID_RE = /^[0-9a-fA-F]{24}$/;
41507
42079
  ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/;
41508
42080
  ObjectId = class _ObjectId {
41509
- static _counter = randomBytes8(3).readUIntBE(0, 3);
41510
- static _process = randomBytes8(5);
42081
+ static _counter = randomBytes9(3).readUIntBE(0, 3);
42082
+ static _process = randomBytes9(5);
41511
42083
  _bytes;
41512
42084
  constructor(oid) {
41513
42085
  if (oid === void 0 || oid === null) {
@@ -41935,7 +42507,7 @@ var init_attachment = __esm({
41935
42507
  });
41936
42508
 
41937
42509
  // src/realtime/storage.ts
41938
- import { randomBytes as randomBytes9 } from "node:crypto";
42510
+ import { randomBytes as randomBytes10 } from "node:crypto";
41939
42511
  import { mkdirSync as mkdirSync20, readFileSync as readFileSync26, writeFileSync as writeFileSync17, unlinkSync as unlinkSync8, statSync as statSync18 } from "node:fs";
41940
42512
  import { resolve as resolve19, sep as sep5 } from "node:path";
41941
42513
  import { createRequire as createRequire8 } from "node:module";
@@ -41946,7 +42518,7 @@ function storageKey(filename = "") {
41946
42518
  const clean = raw.replace(UNSAFE, "").slice(0, 12);
41947
42519
  if (clean) ext = `.${clean}`;
41948
42520
  }
41949
- return `${randomBytes9(16).toString("hex")}${ext}`;
42521
+ return `${randomBytes10(16).toString("hex")}${ext}`;
41950
42522
  }
41951
42523
  function selectStorage(storage) {
41952
42524
  if (storage) return storage;
@@ -42394,6 +42966,7 @@ __export(index_exports, {
42394
42966
  CachedDatabaseAdapter: () => CachedDatabaseAdapter,
42395
42967
  Cursor: () => Cursor,
42396
42968
  DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
42969
+ DEFAULT_SRID: () => DEFAULT_SRID,
42397
42970
  Database: () => Database,
42398
42971
  DatabaseResult: () => DatabaseResult,
42399
42972
  DatabaseUrl: () => DatabaseUrl,
@@ -42409,6 +42982,7 @@ __export(index_exports, {
42409
42982
  NOT_REQUIRED_ON_ADAPTER: () => NOT_REQUIRED_ON_ADAPTER,
42410
42983
  ObjectId: () => ObjectId,
42411
42984
  OdbcAdapter: () => OdbcAdapter,
42985
+ Point: () => Point,
42412
42986
  PostgresAdapter: () => PostgresAdapter,
42413
42987
  QueryBuilder: () => QueryBuilder,
42414
42988
  QueryCache: () => QueryCache,
@@ -42421,6 +42995,7 @@ __export(index_exports, {
42421
42995
  S3Storage: () => S3Storage,
42422
42996
  SQLTranslator: () => SQLTranslator,
42423
42997
  SQLiteAdapter: () => SQLiteAdapter,
42998
+ SpatialNotSupportedError: () => SpatialNotSupportedError,
42424
42999
  SqliteCollection: () => SqliteCollection,
42425
43000
  SqliteDatabase: () => SqliteDatabase,
42426
43001
  adapterColumns: () => adapterColumns,
@@ -42514,6 +43089,7 @@ var init_index = __esm({
42514
43089
  init_baseModel();
42515
43090
  init_queryBuilder();
42516
43091
  init_sqlTranslator();
43092
+ init_point();
42517
43093
  init_connectTimeout();
42518
43094
  init_cachedDatabase();
42519
43095
  init_fakeData2();
@@ -42537,6 +43113,7 @@ export {
42537
43113
  CachedDatabaseAdapter,
42538
43114
  Cursor,
42539
43115
  DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
43116
+ DEFAULT_SRID,
42540
43117
  Database,
42541
43118
  DatabaseResult,
42542
43119
  DatabaseUrl,
@@ -42552,6 +43129,7 @@ export {
42552
43129
  NOT_REQUIRED_ON_ADAPTER,
42553
43130
  ObjectId,
42554
43131
  OdbcAdapter,
43132
+ Point,
42555
43133
  PostgresAdapter,
42556
43134
  QueryBuilder,
42557
43135
  QueryCache,
@@ -42564,6 +43142,7 @@ export {
42564
43142
  S3Storage,
42565
43143
  SQLTranslator,
42566
43144
  SQLiteAdapter,
43145
+ SpatialNotSupportedError,
42567
43146
  SqliteCollection,
42568
43147
  SqliteDatabase,
42569
43148
  adapterColumns,