@rebasepro/server 0.10.0 → 0.10.1-canary.14e53ae

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.
@@ -0,0 +1,83 @@
1
+ import { Hono } from "hono";
2
+ import type { MiddlewareHandler } from "hono";
3
+ import type { HonoEnv } from "../api/types";
4
+ /**
5
+ * Runtime metrics, in Prometheus text format.
6
+ *
7
+ * The point of emitting these from the runtime rather than scraping the
8
+ * container is that only the runtime knows what a request *was*. A pod-level CPU
9
+ * graph cannot tell you that auth is slow while data is fine, or that one app is
10
+ * generating all the traffic. Surface by surface is the difference between a
11
+ * chart that looks like observability and one you can act on.
12
+ *
13
+ * Self-hosters get the same endpoint — this is a plain Prometheus target, not a
14
+ * hook into a hosted platform.
15
+ */
16
+ /** Which part of the API served a request. */
17
+ export type MetricSurface = "data" | "auth" | "storage" | "functions" | "admin" | "meta" | "other";
18
+ export declare class MetricsRegistry {
19
+ private requests;
20
+ private latency;
21
+ private gauges;
22
+ private counters;
23
+ readonly startedAt: number;
24
+ private static labelKey;
25
+ private static parseLabels;
26
+ /**
27
+ * Key a named series.
28
+ *
29
+ * Concatenating a name and its labels without a separator would let metric
30
+ * `rebase_x` with label `y=1` collide with a metric literally named
31
+ * `rebase_xy=1`.
32
+ */
33
+ private static namedKey;
34
+ private static splitNamedKey;
35
+ recordRequest(labels: Record<string, string>, durationMs: number): void;
36
+ incrementCounter(name: string, labels?: Record<string, string>, by?: number): void;
37
+ setGauge(name: string, value: number, labels?: Record<string, string>): void;
38
+ /** Prometheus escaping: backslash, quote and newline, in that order. */
39
+ private static formatLabels;
40
+ /** Group `name -> [[labelKey, value]]` for one-HELP-per-metric rendering. */
41
+ private static group;
42
+ render(): string;
43
+ }
44
+ /**
45
+ * Classify a path into the surface that served it.
46
+ *
47
+ * Path *shape*, never the full path: a label per entity id would create an
48
+ * unbounded set of time series, which is the classic way to take down a
49
+ * Prometheus. Collection slugs are bounded by the schema, so those are safe and
50
+ * genuinely useful.
51
+ */
52
+ export declare function classifySurface(pathname: string, basePath?: string): {
53
+ surface: MetricSurface;
54
+ collection?: string;
55
+ };
56
+ export interface MetricsHandle {
57
+ registry: MetricsRegistry;
58
+ middleware: MiddlewareHandler<HonoEnv>;
59
+ /**
60
+ * Restrict the `collection` label to names that actually exist.
61
+ *
62
+ * Called once the collections are known — the middleware has to be installed
63
+ * before them, since it must wrap every request. Until it is called, and for
64
+ * any name not in the set, the label is dropped: a path segment is attacker-
65
+ * controlled, and one series per value invented is unbounded memory.
66
+ */
67
+ setKnownCollections(slugs: Iterable<string>): void;
68
+ }
69
+ /**
70
+ * Build the request-timing middleware and the registry it feeds.
71
+ *
72
+ * Timing wraps `next()` in a `finally`, so a request that throws is still
73
+ * counted — an endpoint that only ever fails would otherwise be invisible in
74
+ * exactly the situation the metrics exist for.
75
+ */
76
+ export declare function createMetricsMiddleware(basePath?: string): MetricsHandle;
77
+ /**
78
+ * Mount the scrape endpoint.
79
+ *
80
+ * When a token is configured it is required, and compared in constant time — a
81
+ * timing oracle on a metrics token is a small thing, but it is free to avoid.
82
+ */
83
+ export declare function createMetricsRoutes(registry: MetricsRegistry, token?: string): Hono<HonoEnv>;
@@ -490,6 +490,176 @@ function getDataSourceCapabilities(engine) {
490
490
  */
491
491
  var DEFAULT_STORAGE_SOURCE_KEY = "(default)";
492
492
  //#endregion
493
- export { RebaseApiError as _, policy as a, isPostgresCollectionConfig as c, REST_TO_CANONICAL as d, toCanonicalOp as f, Vector as g, GeoPoint as h, isSQLAdmin as i, CANONICAL_TO_REST as l, EntityRelation as m, DEFAULT_DATA_SOURCE_KEY as n, getCollectionDataPath as o, EntityReference as p, getDataSourceCapabilities as r, getDeclaredSubcollections as s, DEFAULT_STORAGE_SOURCE_KEY as t, NULL_OPS as u, RebaseClientError as v };
493
+ //#region ../types/src/types/project_manifest.ts
494
+ /** Header carrying the schema version an SDK was generated from. */
495
+ var SCHEMA_VERSION_HEADER = "x-rebase-schema";
496
+ //#endregion
497
+ //#region ../types/src/types/collection_contract.ts
498
+ /** Depth limit for the walk — deep enough for real configs, finite for cyclic ones. */
499
+ var MAX_DEPTH = 64;
500
+ /**
501
+ * Resolve whatever a `target` thunk returns down to a collection.
502
+ *
503
+ * A target may be the collection, a module namespace (when the authoring file
504
+ * used `import * as`), or a default-export wrapper. All three appear in real
505
+ * projects, and the SDK generator already unwraps them the same way.
506
+ */
507
+ function unwrapTarget(value) {
508
+ if (!value || typeof value !== "object") return void 0;
509
+ const candidate = value;
510
+ if (candidate.default || candidate.__esModule) {
511
+ const inner = candidate.default;
512
+ if (inner && typeof inner === "object") return inner;
513
+ }
514
+ if (candidate.properties) return value;
515
+ }
516
+ /** The identity a serialized reference uses. Slug first — it is the routing key. */
517
+ function refFor(collection) {
518
+ if (!collection) return void 0;
519
+ const withPath = collection;
520
+ return collection.slug || withPath.path || collection.name;
521
+ }
522
+ function toSerializable(value, seen, depth, state, key) {
523
+ if (depth > MAX_DEPTH) {
524
+ state.truncations++;
525
+ return;
526
+ }
527
+ if (typeof value === "function") {
528
+ if (key === "target") try {
529
+ const ref = refFor(unwrapTarget(value()));
530
+ return ref ? { __collectionRef: ref } : void 0;
531
+ } catch {
532
+ return;
533
+ }
534
+ return;
535
+ }
536
+ if (value === null || typeof value !== "object") return value;
537
+ if (value instanceof Date) return value.toISOString();
538
+ if (value instanceof RegExp) return value.source;
539
+ if (seen.has(value)) {
540
+ state.truncations++;
541
+ return;
542
+ }
543
+ const cached = state.memo.get(value);
544
+ if (cached !== void 0) return cached;
545
+ seen.add(value);
546
+ const truncationsBefore = state.truncations;
547
+ const memoize = (result) => {
548
+ if (result !== void 0 && state.truncations === truncationsBefore) state.memo.set(value, result);
549
+ return result;
550
+ };
551
+ try {
552
+ if (Array.isArray(value)) {
553
+ const items = value.map((item) => toSerializable(item, seen, depth + 1, state)).filter((item) => item !== void 0);
554
+ return memoize(value.length > 0 && items.length === 0 ? void 0 : items);
555
+ }
556
+ if ("$$typeof" in value) return void 0;
557
+ const entries = Object.entries(value);
558
+ const out = {};
559
+ for (const [k, v] of entries) {
560
+ const converted = toSerializable(v, seen, depth + 1, state, k);
561
+ if (converted !== void 0) out[k] = converted;
562
+ }
563
+ if (entries.length > 0 && Object.keys(out).length === 0) return void 0;
564
+ return memoize(out);
565
+ } finally {
566
+ seen.delete(value);
567
+ }
568
+ }
569
+ /**
570
+ * Serialize collections for transport over the contract endpoint.
571
+ *
572
+ * Sorted by slug so the output — and therefore the schema hash computed from it
573
+ * — does not depend on filesystem ordering.
574
+ */
575
+ function serializeCollections(collections) {
576
+ return [...collections].sort((a, b) => String(a.slug ?? "").localeCompare(String(b.slug ?? ""))).map((collection) => toSerializable(collection, /* @__PURE__ */ new WeakSet(), 0, {
577
+ memo: /* @__PURE__ */ new WeakMap(),
578
+ truncations: 0
579
+ })).filter((c) => c !== void 0);
580
+ }
581
+ //#endregion
582
+ //#region ../types/src/types/schema_version.ts
583
+ /**
584
+ * The schema version stamp.
585
+ *
586
+ * One function, used in three places that must agree or the whole drift-detection
587
+ * story is noise: `rebase build` writes it into a bundle manifest, the runtime
588
+ * serves it from the contract endpoint, and a generated SDK records the value it
589
+ * was built from. If any two of those computed it differently, every client would
590
+ * look permanently out of date.
591
+ *
592
+ * It covers **collections only** — the client's contract is the shape of the
593
+ * data, so editing a hook or a server function must not invalidate every SDK in
594
+ * every repository. That is a deliberate narrowing, not an oversight.
595
+ */
596
+ /** Stable stringify: object keys sorted at every level, so key order cannot alter the hash. */
597
+ function canonicalize(value) {
598
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
599
+ if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
600
+ return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalize(v)}`).join(",")}}`;
601
+ }
602
+ /**
603
+ * Reduce a collection to the parts a generated client is actually built from.
604
+ *
605
+ * The version answers one question — "is this SDK stale?" — so it must change
606
+ * exactly when the generated types could change, and never otherwise. Hashing a
607
+ * whole collection fails both halves of that:
608
+ *
609
+ * - Security rules, callbacks, icons, groups and UI settings do not appear in a
610
+ * generated client, so including them reports perfectly current SDKs as stale.
611
+ * - Worse, they are not stable *inputs*. The runtime applies default security
612
+ * rules when it loads collections, so the same source hashed before and after
613
+ * loading produced two different answers — a build-time stamp that could never
614
+ * match the server that served it.
615
+ *
616
+ * Codegen reads the slug (for the `Database` key and type names), the properties,
617
+ * and the relations. That is the projection.
618
+ */
619
+ function projectForCodegen(collection) {
620
+ const source = collection;
621
+ return {
622
+ slug: collection.slug ?? source.path,
623
+ properties: collection.properties,
624
+ relations: source.relations,
625
+ engine: source.engine,
626
+ dataSource: source.dataSource,
627
+ subcollections: source.subcollections?.map(projectForCodegen)
628
+ };
629
+ }
630
+ /**
631
+ * Compute the canonical string a schema version hashes.
632
+ *
633
+ * Exposed separately so the hashing itself can differ by environment: Node has
634
+ * `crypto`, and callers without it can still compare canonical forms directly.
635
+ */
636
+ function canonicalSchemaPayload(collections) {
637
+ return canonicalize(serializeCollections(collections).map((collection) => projectForCodegen(collection)));
638
+ }
639
+ /**
640
+ * A short, non-cryptographic digest of the canonical payload.
641
+ *
642
+ * FNV-1a style, 64 bits, as two 32-bit halves. This is an identity, not a
643
+ * security boundary: nothing trusts a schema version to prove anything, it only
644
+ * answers "is this the same schema as before". A hand-rolled hash keeps this
645
+ * module free of `node:crypto`, so the identical function runs in the browser,
646
+ * in the CLI, and in the runtime — which is the property that actually matters.
647
+ */
648
+ function computeSchemaVersion(collections) {
649
+ const payload = canonicalSchemaPayload(collections);
650
+ let h1 = 2166136261;
651
+ let h2 = 16777619;
652
+ for (let i = 0; i < payload.length; i++) {
653
+ const code = payload.charCodeAt(i);
654
+ h1 ^= code;
655
+ h1 = h1 + ((h1 << 1) + (h1 << 4) + (h1 << 7) + (h1 << 8) + (h1 << 24)) >>> 0;
656
+ h2 ^= code + i;
657
+ h2 = h2 + ((h2 << 1) + (h2 << 5) + (h2 << 9) + (h2 << 15) + (h2 << 24)) >>> 0;
658
+ }
659
+ const hex = (n) => n.toString(16).padStart(8, "0");
660
+ return `v1:${hex(h1)}${hex(h2)}`;
661
+ }
662
+ //#endregion
663
+ export { EntityRelation as _, DEFAULT_DATA_SOURCE_KEY as a, RebaseApiError as b, policy as c, isPostgresCollectionConfig as d, CANONICAL_TO_REST as f, EntityReference as g, toCanonicalOp as h, DEFAULT_STORAGE_SOURCE_KEY as i, getCollectionDataPath as l, REST_TO_CANONICAL as m, serializeCollections as n, getDataSourceCapabilities as o, NULL_OPS as p, SCHEMA_VERSION_HEADER as r, isSQLAdmin as s, computeSchemaVersion as t, getDeclaredSubcollections as u, GeoPoint as v, RebaseClientError as x, Vector as y };
494
664
 
495
- //# sourceMappingURL=src-CsHhSKbi.js.map
665
+ //# sourceMappingURL=src-BITicbgD.js.map