@orthacms/utils-server 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  export { parseFilterTree } from './lib/filters/parse-filter-tree';
2
2
  export { applyFilterTree, type ApplyFilterTreeOptions, type FilterExtensionResolver } from './lib/filters/tree-to-drizzle';
3
3
  export type { DbLike, TableLike } from './lib/filters/table-helpers';
4
- export { FilterOperator, ScalarFieldType, RelationKind } from './lib/filters/types';
4
+ export { FilterOperator, ScalarFieldType, RelationKind, WithinLastUnit } from './lib/filters/types';
5
5
  export { OPERATORS_BY_TYPE, operatorsFor } from './lib/filters/operator-support';
6
- export type { ScalarFieldSchema, FieldSchema, RelationSchema, RelationScope, FilterSchema, ParsedRule, ParsedGroup, ParsedNode } from './lib/filters/types';
6
+ export type { WithinLastValue, ScalarFieldSchema, FieldSchema, RelationSchema, RelationScope, FilterSchema, ParsedRule, ParsedGroup, ParsedNode } from './lib/filters/types';
7
7
  export { FilterException, FilterErrorCode, FilterSchemaException } from './lib/filters/filter-exceptions';
8
8
  export { clampInt } from './lib/clamp-int';
9
9
  export { isForeignKeyViolation, isUniqueViolation, violatedConstraint } from './lib/pg-errors';
10
+ export { readEnv, requireEnv, readPositiveInt, readOptionalPositiveInt, readList, readOptionalList, readFlag, readTrustProxy, readNodeEnv, isProduction, when, defined, NODE_ENVS, type NodeEnv } from './lib/env';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EACH,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC/B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACH,cAAc,EACd,eAAe,EACf,YAAY,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACf,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACR,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,EACV,WAAW,EACX,UAAU,EACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,eAAe,EACf,eAAe,EACf,qBAAqB,EACxB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EACrB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EACH,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC/B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACH,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACf,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACR,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,EACV,WAAW,EACX,UAAU,EACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,eAAe,EACf,eAAe,EACf,qBAAqB,EACxB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACH,OAAO,EACP,UAAU,EACV,eAAe,EACf,uBAAuB,EACvB,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,WAAW,EACX,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,SAAS,EACT,KAAK,OAAO,EACf,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.violatedConstraint = exports.isUniqueViolation = exports.isForeignKeyViolation = exports.clampInt = exports.FilterSchemaException = exports.FilterErrorCode = exports.FilterException = exports.operatorsFor = exports.OPERATORS_BY_TYPE = exports.RelationKind = exports.ScalarFieldType = exports.FilterOperator = exports.applyFilterTree = exports.parseFilterTree = void 0;
3
+ exports.NODE_ENVS = exports.defined = exports.when = exports.isProduction = exports.readNodeEnv = exports.readTrustProxy = exports.readFlag = exports.readOptionalList = exports.readList = exports.readOptionalPositiveInt = exports.readPositiveInt = exports.requireEnv = exports.readEnv = exports.violatedConstraint = exports.isUniqueViolation = exports.isForeignKeyViolation = exports.clampInt = exports.FilterSchemaException = exports.FilterErrorCode = exports.FilterException = exports.operatorsFor = exports.OPERATORS_BY_TYPE = exports.WithinLastUnit = exports.RelationKind = exports.ScalarFieldType = exports.FilterOperator = exports.applyFilterTree = exports.parseFilterTree = void 0;
4
4
  var parse_filter_tree_1 = require("./lib/filters/parse-filter-tree");
5
5
  Object.defineProperty(exports, "parseFilterTree", { enumerable: true, get: function () { return parse_filter_tree_1.parseFilterTree; } });
6
6
  var tree_to_drizzle_1 = require("./lib/filters/tree-to-drizzle");
@@ -9,6 +9,7 @@ var types_1 = require("./lib/filters/types");
9
9
  Object.defineProperty(exports, "FilterOperator", { enumerable: true, get: function () { return types_1.FilterOperator; } });
10
10
  Object.defineProperty(exports, "ScalarFieldType", { enumerable: true, get: function () { return types_1.ScalarFieldType; } });
11
11
  Object.defineProperty(exports, "RelationKind", { enumerable: true, get: function () { return types_1.RelationKind; } });
12
+ Object.defineProperty(exports, "WithinLastUnit", { enumerable: true, get: function () { return types_1.WithinLastUnit; } });
12
13
  var operator_support_1 = require("./lib/filters/operator-support");
13
14
  Object.defineProperty(exports, "OPERATORS_BY_TYPE", { enumerable: true, get: function () { return operator_support_1.OPERATORS_BY_TYPE; } });
14
15
  Object.defineProperty(exports, "operatorsFor", { enumerable: true, get: function () { return operator_support_1.operatorsFor; } });
@@ -22,3 +23,17 @@ var pg_errors_1 = require("./lib/pg-errors");
22
23
  Object.defineProperty(exports, "isForeignKeyViolation", { enumerable: true, get: function () { return pg_errors_1.isForeignKeyViolation; } });
23
24
  Object.defineProperty(exports, "isUniqueViolation", { enumerable: true, get: function () { return pg_errors_1.isUniqueViolation; } });
24
25
  Object.defineProperty(exports, "violatedConstraint", { enumerable: true, get: function () { return pg_errors_1.violatedConstraint; } });
26
+ var env_1 = require("./lib/env");
27
+ Object.defineProperty(exports, "readEnv", { enumerable: true, get: function () { return env_1.readEnv; } });
28
+ Object.defineProperty(exports, "requireEnv", { enumerable: true, get: function () { return env_1.requireEnv; } });
29
+ Object.defineProperty(exports, "readPositiveInt", { enumerable: true, get: function () { return env_1.readPositiveInt; } });
30
+ Object.defineProperty(exports, "readOptionalPositiveInt", { enumerable: true, get: function () { return env_1.readOptionalPositiveInt; } });
31
+ Object.defineProperty(exports, "readList", { enumerable: true, get: function () { return env_1.readList; } });
32
+ Object.defineProperty(exports, "readOptionalList", { enumerable: true, get: function () { return env_1.readOptionalList; } });
33
+ Object.defineProperty(exports, "readFlag", { enumerable: true, get: function () { return env_1.readFlag; } });
34
+ Object.defineProperty(exports, "readTrustProxy", { enumerable: true, get: function () { return env_1.readTrustProxy; } });
35
+ Object.defineProperty(exports, "readNodeEnv", { enumerable: true, get: function () { return env_1.readNodeEnv; } });
36
+ Object.defineProperty(exports, "isProduction", { enumerable: true, get: function () { return env_1.isProduction; } });
37
+ Object.defineProperty(exports, "when", { enumerable: true, get: function () { return env_1.when; } });
38
+ Object.defineProperty(exports, "defined", { enumerable: true, get: function () { return env_1.defined; } });
39
+ Object.defineProperty(exports, "NODE_ENVS", { enumerable: true, get: function () { return env_1.NODE_ENVS; } });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Reading `process.env` into typed configuration.
3
+ *
4
+ * A host's `ortha.config.ts` is the one file allowed to touch the environment,
5
+ * and every deployment writes the same handful of readers to do it: a required
6
+ * string, a bounded number, a comma list, `TRUST_PROXY`, `NODE_ENV`. They were
7
+ * duplicated between this repo's host and the scaffolder's template, where the
8
+ * copies had already drifted — the template's number reader had no
9
+ * "optional, no default" form, so a generated app could not express a ceiling
10
+ * that means "leave the plugin's own default alone".
11
+ *
12
+ * Every reader here **throws rather than guesses**. That is the whole point of
13
+ * the module: the failure mode of environment parsing is silence, and a config
14
+ * value that is quietly wrong is discovered a session TTL later, or by reading
15
+ * a `Set-Cookie` header, rather than at boot with the variable named.
16
+ *
17
+ * The last two exports — {@link when} and {@link defined} — read nothing. They
18
+ * are here because they are what a host does with the readers' `undefined`:
19
+ * plugins merge their settings as `{ ...DEFAULTS, ...config }`, so an unset
20
+ * value must become an *absent key* rather than an explicit `undefined`, which
21
+ * would erase the default it was meant to leave alone. Every host writing that
22
+ * as `...(x ? { key } : {})` at each call site is the duplication this module
23
+ * exists to end.
24
+ */
25
+ /**
26
+ * A trimmed environment value, `undefined` when unset **or empty**.
27
+ *
28
+ * The one reader every other reader here is built on, so "empty means not
29
+ * configured" is decided once. That equivalence matters: `.env.example` ships
30
+ * keys with no value, and `ANTHROPIC_API_KEY=` has to leave a deployment with
31
+ * *no* Claude backend rather than one registered with an empty key — a backend
32
+ * that is in the picker and fails on the first message.
33
+ */
34
+ export declare function readEnv(name: string): string | undefined;
35
+ /**
36
+ * Reads a value the app cannot run without, failing at load rather than
37
+ * several seconds into boot.
38
+ *
39
+ * Left to default to `''`, a missing `DATABASE_URL` reaches `pg` as "use the
40
+ * libpq defaults", and the first thing that touches the database fails with
41
+ * whatever the local libpq environment happens to produce (measured: `SASL:
42
+ * SCRAM-SERVER-FIRST-MESSAGE: client password must be a string`). The server
43
+ * does fail closed, which is the important half, but nothing in that message
44
+ * names the variable that was never set.
45
+ *
46
+ * @param name The environment variable to read.
47
+ * @param hint Appended to the error, for a deployment-specific instruction —
48
+ * which file to copy, which secret store to look in. Omitted, the message
49
+ * says only that there is no usable default.
50
+ */
51
+ export declare function requireEnv(name: string, hint?: string): string;
52
+ /**
53
+ * Reads a numeric setting: the default when unset or empty, the value when it
54
+ * is a plain positive decimal integer, and an error otherwise.
55
+ *
56
+ * This replaces `Number(process.env[x]) || default`, which was wrong in three
57
+ * directions at once and silent in all of them. `0` is falsy, so it became the
58
+ * default — `LOGIN_RATE_LIMIT=0` ("block every login") quietly meant 10. A
59
+ * negative is truthy, so it was accepted — `SESSION_TTL_SECONDS=-1` issued
60
+ * every session already expired, login answering `201` and the very next
61
+ * request `401`. And exponent notation parsed, so `GRAPHQL_MAX_DEPTH=1e9`
62
+ * removed the cost budget that ADR-0008 calls GraphQL's replacement for REST's
63
+ * structural bound. Refusing to boot names the variable; the alternative was a
64
+ * deployment that looked configured and was not.
65
+ *
66
+ * Empty is deliberately *not* an error: `.env.example` ships several keys with
67
+ * no value, and a fresh clone must boot from it unchanged.
68
+ */
69
+ export declare function readPositiveInt(name: string, fallback: number): number;
70
+ /**
71
+ * As {@link readPositiveInt}, but `undefined` when unset — no default to fall
72
+ * back to.
73
+ *
74
+ * The form a *ceiling* needs. A plugin that ships its own default cannot be
75
+ * handed `{ maxSteps: undefined }`, because spreading that overwrites the
76
+ * default with nothing; the caller conditionally spreads on this instead.
77
+ */
78
+ export declare function readOptionalPositiveInt(name: string): number | undefined;
79
+ /**
80
+ * A comma-separated list setting, trimmed and emptied of blanks.
81
+ *
82
+ * An explicitly empty value yields an empty list rather than the fallback,
83
+ * because "allow no origins" is a setting somebody means.
84
+ */
85
+ export declare function readList(name: string, fallback: string): string[];
86
+ /**
87
+ * Express's `trust proxy` setting, in the three shapes it accepts.
88
+ *
89
+ * Checked in this order: a hop count (`'1'` — the recommended form, and the
90
+ * only one a client cannot forge past), a boolean (`'true'` trusts the entire
91
+ * `X-Forwarded-For` chain, `'false'` trusts none), or any other non-empty
92
+ * string, passed to Express verbatim as a subnet/preset list (`'loopback'`,
93
+ * `'10.0.0.0/8'`). Unset yields `undefined`, leaving Express's default of
94
+ * ignoring forwarded headers entirely.
95
+ *
96
+ * Returns the union structurally rather than importing `TrustProxySetting`
97
+ * from `@orthacms/bootstrap-server`: this is a leaf helper package, and the
98
+ * host importing it must not become a dependency of it. The host's own
99
+ * `trustProxy?: TrustProxySetting` field is what checks the two agree.
100
+ */
101
+ export declare function readTrustProxy(name?: string): boolean | number | string | undefined;
102
+ /** The deployment modes an Ortha app recognises. */
103
+ export declare const NODE_ENVS: readonly ["development", "test", "production"];
104
+ /** One of {@link NODE_ENVS}. */
105
+ export type NodeEnv = (typeof NODE_ENVS)[number];
106
+ /**
107
+ * Reads `NODE_ENV`, rejecting a value that is neither recognised nor empty.
108
+ *
109
+ * `NODE_ENV !== 'production'` is the switch behind **two** protections at once
110
+ * — whether the API reference and GraphiQL are published, and whether the
111
+ * session cookie carries `Secure` — so any value that is not exactly
112
+ * `production` turns both off. Unset is a legitimate, and the common, local
113
+ * state; a *typo* is not, and it is indistinguishable from correct
114
+ * configuration until you read a `Set-Cookie` header. Measured: `produciton`
115
+ * serves `/reference/json` to an unauthenticated caller and drops `Secure`
116
+ * from the session cookie, exactly as if nothing had been set (ORT-137).
117
+ *
118
+ * Rejecting the typo costs a deployment that spells it right nothing, and
119
+ * turns a silent downgrade into a refusal to start.
120
+ */
121
+ export declare function readNodeEnv(): NodeEnv | undefined;
122
+ /**
123
+ * True only in a deployment that said `production`, with the spelling checked
124
+ * by {@link readNodeEnv}.
125
+ */
126
+ export declare function isProduction(): boolean;
127
+ /**
128
+ * As {@link readList}, but `undefined` when the variable is unset, so the
129
+ * consumer's own default list survives rather than being replaced by an empty
130
+ * one.
131
+ *
132
+ * The list counterpart to {@link readOptionalPositiveInt}, and needed for the
133
+ * same reason: a setting whose owner ships a default cannot be handed an empty
134
+ * array meaning "the deployment said nothing".
135
+ */
136
+ export declare function readOptionalList(name: string): string[] | undefined;
137
+ /**
138
+ * A boolean setting: what the deployment said, or `fallback` when it said
139
+ * nothing.
140
+ *
141
+ * Anything other than `true` reads as false, so a typo turns a switch **off**
142
+ * rather than on. That direction is deliberate: the switches this reads are
143
+ * kill switches for surfaces that send content to a third party or open a door
144
+ * for an external agent, and a misspelling should not open one.
145
+ */
146
+ export declare function readFlag(name: string, fallback: boolean): boolean;
147
+ /**
148
+ * The value when the setting was configured, `undefined` when it was not.
149
+ *
150
+ * The counterpart to {@link defined}: together they replace
151
+ * `...(x ? { key: … } : {})` at a config site. `build` is a thunk so its body —
152
+ * often several further reads — runs only when it applies, which is what lets a
153
+ * whole provider block sit behind one credential check.
154
+ */
155
+ export declare function when<T>(configured: unknown, build: () => T): T | undefined;
156
+ /**
157
+ * The same object with every `undefined`-valued key removed.
158
+ *
159
+ * A plugin merges its own defaults as `{ ...DEFAULTS, ...config }`, so an
160
+ * explicit `{ maxSteps: undefined }` does not leave `DEFAULT_RUN_LIMITS.maxSteps`
161
+ * in place — it erases it. Dropping the key is what "the deployment did not set
162
+ * this" has to mean, and saying it once at the end of a builder is why a config
163
+ * file no longer needs a conditional per optional setting.
164
+ */
165
+ export declare function defined<T extends object>(value: T): T;
166
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/lib/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAS9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcxE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAKjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC1B,IAAI,SAAgB,GACrB,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAavC;AAED,oDAAoD;AACpD,eAAO,MAAM,SAAS,gDAAiD,CAAC;AAExE,gCAAgC;AAChC,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,IAAI,OAAO,GAAG,SAAS,CAejD;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAEnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAGjE;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAE1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAQrD"}
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ /**
3
+ * Reading `process.env` into typed configuration.
4
+ *
5
+ * A host's `ortha.config.ts` is the one file allowed to touch the environment,
6
+ * and every deployment writes the same handful of readers to do it: a required
7
+ * string, a bounded number, a comma list, `TRUST_PROXY`, `NODE_ENV`. They were
8
+ * duplicated between this repo's host and the scaffolder's template, where the
9
+ * copies had already drifted — the template's number reader had no
10
+ * "optional, no default" form, so a generated app could not express a ceiling
11
+ * that means "leave the plugin's own default alone".
12
+ *
13
+ * Every reader here **throws rather than guesses**. That is the whole point of
14
+ * the module: the failure mode of environment parsing is silence, and a config
15
+ * value that is quietly wrong is discovered a session TTL later, or by reading
16
+ * a `Set-Cookie` header, rather than at boot with the variable named.
17
+ *
18
+ * The last two exports — {@link when} and {@link defined} — read nothing. They
19
+ * are here because they are what a host does with the readers' `undefined`:
20
+ * plugins merge their settings as `{ ...DEFAULTS, ...config }`, so an unset
21
+ * value must become an *absent key* rather than an explicit `undefined`, which
22
+ * would erase the default it was meant to leave alone. Every host writing that
23
+ * as `...(x ? { key } : {})` at each call site is the duplication this module
24
+ * exists to end.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.NODE_ENVS = void 0;
28
+ exports.readEnv = readEnv;
29
+ exports.requireEnv = requireEnv;
30
+ exports.readPositiveInt = readPositiveInt;
31
+ exports.readOptionalPositiveInt = readOptionalPositiveInt;
32
+ exports.readList = readList;
33
+ exports.readTrustProxy = readTrustProxy;
34
+ exports.readNodeEnv = readNodeEnv;
35
+ exports.isProduction = isProduction;
36
+ exports.readOptionalList = readOptionalList;
37
+ exports.readFlag = readFlag;
38
+ exports.when = when;
39
+ exports.defined = defined;
40
+ /**
41
+ * A trimmed environment value, `undefined` when unset **or empty**.
42
+ *
43
+ * The one reader every other reader here is built on, so "empty means not
44
+ * configured" is decided once. That equivalence matters: `.env.example` ships
45
+ * keys with no value, and `ANTHROPIC_API_KEY=` has to leave a deployment with
46
+ * *no* Claude backend rather than one registered with an empty key — a backend
47
+ * that is in the picker and fails on the first message.
48
+ */
49
+ function readEnv(name) {
50
+ return process.env[name]?.trim() || undefined;
51
+ }
52
+ /**
53
+ * Reads a value the app cannot run without, failing at load rather than
54
+ * several seconds into boot.
55
+ *
56
+ * Left to default to `''`, a missing `DATABASE_URL` reaches `pg` as "use the
57
+ * libpq defaults", and the first thing that touches the database fails with
58
+ * whatever the local libpq environment happens to produce (measured: `SASL:
59
+ * SCRAM-SERVER-FIRST-MESSAGE: client password must be a string`). The server
60
+ * does fail closed, which is the important half, but nothing in that message
61
+ * names the variable that was never set.
62
+ *
63
+ * @param name The environment variable to read.
64
+ * @param hint Appended to the error, for a deployment-specific instruction —
65
+ * which file to copy, which secret store to look in. Omitted, the message
66
+ * says only that there is no usable default.
67
+ */
68
+ function requireEnv(name, hint) {
69
+ const raw = readEnv(name);
70
+ if (!raw) {
71
+ throw new Error(`Missing required environment variable ${name}. ` +
72
+ (hint ?? 'The server has no usable default for this value.'));
73
+ }
74
+ return raw;
75
+ }
76
+ /**
77
+ * Reads a numeric setting: the default when unset or empty, the value when it
78
+ * is a plain positive decimal integer, and an error otherwise.
79
+ *
80
+ * This replaces `Number(process.env[x]) || default`, which was wrong in three
81
+ * directions at once and silent in all of them. `0` is falsy, so it became the
82
+ * default — `LOGIN_RATE_LIMIT=0` ("block every login") quietly meant 10. A
83
+ * negative is truthy, so it was accepted — `SESSION_TTL_SECONDS=-1` issued
84
+ * every session already expired, login answering `201` and the very next
85
+ * request `401`. And exponent notation parsed, so `GRAPHQL_MAX_DEPTH=1e9`
86
+ * removed the cost budget that ADR-0008 calls GraphQL's replacement for REST's
87
+ * structural bound. Refusing to boot names the variable; the alternative was a
88
+ * deployment that looked configured and was not.
89
+ *
90
+ * Empty is deliberately *not* an error: `.env.example` ships several keys with
91
+ * no value, and a fresh clone must boot from it unchanged.
92
+ */
93
+ function readPositiveInt(name, fallback) {
94
+ return readOptionalPositiveInt(name) ?? fallback;
95
+ }
96
+ /**
97
+ * As {@link readPositiveInt}, but `undefined` when unset — no default to fall
98
+ * back to.
99
+ *
100
+ * The form a *ceiling* needs. A plugin that ships its own default cannot be
101
+ * handed `{ maxSteps: undefined }`, because spreading that overwrites the
102
+ * default with nothing; the caller conditionally spreads on this instead.
103
+ */
104
+ function readOptionalPositiveInt(name) {
105
+ const raw = readEnv(name);
106
+ if (!raw) {
107
+ return undefined;
108
+ }
109
+ // Plain decimal digits only. `Number` would also take `1e9`, `0x20` and
110
+ // `Infinity`, none of which anyone means to write in a `.env`.
111
+ if (!/^\d+$/.test(raw) || Number(raw) <= 0) {
112
+ throw new Error(`Environment variable ${name} must be a positive whole number ` +
113
+ `(got "${raw}").`);
114
+ }
115
+ return Number(raw);
116
+ }
117
+ /**
118
+ * A comma-separated list setting, trimmed and emptied of blanks.
119
+ *
120
+ * An explicitly empty value yields an empty list rather than the fallback,
121
+ * because "allow no origins" is a setting somebody means.
122
+ */
123
+ function readList(name, fallback) {
124
+ return (process.env[name] ?? fallback)
125
+ .split(',')
126
+ .map((item) => item.trim())
127
+ .filter(Boolean);
128
+ }
129
+ /**
130
+ * Express's `trust proxy` setting, in the three shapes it accepts.
131
+ *
132
+ * Checked in this order: a hop count (`'1'` — the recommended form, and the
133
+ * only one a client cannot forge past), a boolean (`'true'` trusts the entire
134
+ * `X-Forwarded-For` chain, `'false'` trusts none), or any other non-empty
135
+ * string, passed to Express verbatim as a subnet/preset list (`'loopback'`,
136
+ * `'10.0.0.0/8'`). Unset yields `undefined`, leaving Express's default of
137
+ * ignoring forwarded headers entirely.
138
+ *
139
+ * Returns the union structurally rather than importing `TrustProxySetting`
140
+ * from `@orthacms/bootstrap-server`: this is a leaf helper package, and the
141
+ * host importing it must not become a dependency of it. The host's own
142
+ * `trustProxy?: TrustProxySetting` field is what checks the two agree.
143
+ */
144
+ function readTrustProxy(name = 'TRUST_PROXY') {
145
+ const raw = readEnv(name);
146
+ if (!raw) {
147
+ return undefined;
148
+ }
149
+ const hops = Number(raw);
150
+ if (Number.isInteger(hops) && hops >= 0) {
151
+ return hops;
152
+ }
153
+ if (raw === 'true' || raw === 'false') {
154
+ return raw === 'true';
155
+ }
156
+ return raw;
157
+ }
158
+ /** The deployment modes an Ortha app recognises. */
159
+ exports.NODE_ENVS = ['development', 'test', 'production'];
160
+ /**
161
+ * Reads `NODE_ENV`, rejecting a value that is neither recognised nor empty.
162
+ *
163
+ * `NODE_ENV !== 'production'` is the switch behind **two** protections at once
164
+ * — whether the API reference and GraphiQL are published, and whether the
165
+ * session cookie carries `Secure` — so any value that is not exactly
166
+ * `production` turns both off. Unset is a legitimate, and the common, local
167
+ * state; a *typo* is not, and it is indistinguishable from correct
168
+ * configuration until you read a `Set-Cookie` header. Measured: `produciton`
169
+ * serves `/reference/json` to an unauthenticated caller and drops `Secure`
170
+ * from the session cookie, exactly as if nothing had been set (ORT-137).
171
+ *
172
+ * Rejecting the typo costs a deployment that spells it right nothing, and
173
+ * turns a silent downgrade into a refusal to start.
174
+ */
175
+ function readNodeEnv() {
176
+ const raw = readEnv('NODE_ENV');
177
+ if (!raw) {
178
+ return undefined;
179
+ }
180
+ if (!exports.NODE_ENVS.includes(raw)) {
181
+ throw new Error(`NODE_ENV is "${raw}", which this app does not recognise — expected ` +
182
+ `one of ${exports.NODE_ENVS.join(', ')}, or nothing at all for local ` +
183
+ 'development. Anything else reads as "not production", which ' +
184
+ 'publishes the API reference and drops `Secure` from the session ' +
185
+ 'cookie.');
186
+ }
187
+ return raw;
188
+ }
189
+ /**
190
+ * True only in a deployment that said `production`, with the spelling checked
191
+ * by {@link readNodeEnv}.
192
+ */
193
+ function isProduction() {
194
+ return readNodeEnv() === 'production';
195
+ }
196
+ /**
197
+ * As {@link readList}, but `undefined` when the variable is unset, so the
198
+ * consumer's own default list survives rather than being replaced by an empty
199
+ * one.
200
+ *
201
+ * The list counterpart to {@link readOptionalPositiveInt}, and needed for the
202
+ * same reason: a setting whose owner ships a default cannot be handed an empty
203
+ * array meaning "the deployment said nothing".
204
+ */
205
+ function readOptionalList(name) {
206
+ return when(readEnv(name), () => readList(name, ''));
207
+ }
208
+ /**
209
+ * A boolean setting: what the deployment said, or `fallback` when it said
210
+ * nothing.
211
+ *
212
+ * Anything other than `true` reads as false, so a typo turns a switch **off**
213
+ * rather than on. That direction is deliberate: the switches this reads are
214
+ * kill switches for surfaces that send content to a third party or open a door
215
+ * for an external agent, and a misspelling should not open one.
216
+ */
217
+ function readFlag(name, fallback) {
218
+ const raw = readEnv(name);
219
+ return raw === undefined ? fallback : raw === 'true';
220
+ }
221
+ /**
222
+ * The value when the setting was configured, `undefined` when it was not.
223
+ *
224
+ * The counterpart to {@link defined}: together they replace
225
+ * `...(x ? { key: … } : {})` at a config site. `build` is a thunk so its body —
226
+ * often several further reads — runs only when it applies, which is what lets a
227
+ * whole provider block sit behind one credential check.
228
+ */
229
+ function when(configured, build) {
230
+ return configured ? build() : undefined;
231
+ }
232
+ /**
233
+ * The same object with every `undefined`-valued key removed.
234
+ *
235
+ * A plugin merges its own defaults as `{ ...DEFAULTS, ...config }`, so an
236
+ * explicit `{ maxSteps: undefined }` does not leave `DEFAULT_RUN_LIMITS.maxSteps`
237
+ * in place — it erases it. Dropping the key is what "the deployment did not set
238
+ * this" has to mean, and saying it once at the end of a builder is why a config
239
+ * file no longer needs a conditional per optional setting.
240
+ */
241
+ function defined(value) {
242
+ const result = {};
243
+ for (const [key, item] of Object.entries(value)) {
244
+ if (item !== undefined) {
245
+ result[key] = item;
246
+ }
247
+ }
248
+ return result;
249
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"operator-support.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/operator-support.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAsD1D;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAClC,eAAe,EACf,SAAS,cAAc,EAAE,CAQ5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,eAAe,GACtB,SAAS,cAAc,EAAE,GAAG,SAAS,CAIvC"}
1
+ {"version":3,"file":"operator-support.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/operator-support.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmE1D;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAClC,eAAe,EACf,SAAS,cAAc,EAAE,CAQ5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,eAAe,GACtB,SAAS,cAAc,EAAE,GAAG,SAAS,CAIvC"}
@@ -47,6 +47,18 @@ const COMPARABLE_OPERATORS = [
47
47
  types_1.FilterOperator.Nin,
48
48
  types_1.FilterOperator.Null
49
49
  ];
50
+ /**
51
+ * {@link COMPARABLE_OPERATORS} plus the relative window.
52
+ *
53
+ * `within_last` is date-only because it compares against `now()` — asking for
54
+ * "titles within the last 7 days" is not a narrower query, it is a category
55
+ * error, and Postgres would report it as a type mismatch rather than as
56
+ * something the client can fix.
57
+ */
58
+ const DATE_OPERATORS = [
59
+ ...COMPARABLE_OPERATORS,
60
+ types_1.FilterOperator.WithinLast
61
+ ];
50
62
  /** {@link COMPARABLE_OPERATORS} plus the text-only `~~` family. */
51
63
  const TEXT_OPERATORS = [
52
64
  ...COMPARABLE_OPERATORS,
@@ -67,7 +79,7 @@ exports.OPERATORS_BY_TYPE = {
67
79
  [types_1.ScalarFieldType.Number]: COMPARABLE_OPERATORS,
68
80
  [types_1.ScalarFieldType.Boolean]: COMPARABLE_OPERATORS,
69
81
  [types_1.ScalarFieldType.Uuid]: COMPARABLE_OPERATORS,
70
- [types_1.ScalarFieldType.Date]: COMPARABLE_OPERATORS
82
+ [types_1.ScalarFieldType.Date]: DATE_OPERATORS
71
83
  };
72
84
  /**
73
85
  * The operators `type` accepts, or `undefined` when the type is not one this
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-leaf.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/resolve-leaf.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAqB,MAAM,SAAS,CAAC;AAQ7E;;;;GAIG;AACH,wBAAgB,WAAW,CACvB,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACxB,YAAY,CAkEd"}
1
+ {"version":3,"file":"resolve-leaf.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/resolve-leaf.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACR,YAAY,EACZ,YAAY,EAGf,MAAM,SAAS,CAAC;AAQjB;;;;GAIG;AACH,wBAAgB,WAAW,CACvB,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACxB,YAAY,CAkEd"}
@@ -71,6 +71,9 @@ function assertOperatorAllowed(field, op, path) {
71
71
  }
72
72
  function coerce(raw, field, op, path, maxInListLength) {
73
73
  const pathStr = path.join('.');
74
+ if (op === types_1.FilterOperator.WithinLast) {
75
+ return withinLastValue(raw, pathStr);
76
+ }
74
77
  if (op === types_1.FilterOperator.Null) {
75
78
  if (raw === 'true' || raw === true)
76
79
  return true;
@@ -100,6 +103,34 @@ function coerce(raw, field, op, path, maxInListLength) {
100
103
  }
101
104
  return scalarOf(raw, field, pathStr);
102
105
  }
106
+ /** Units a `within_last` window may name, as a set for membership checks. */
107
+ const WITHIN_LAST_UNITS = new Set(Object.values(types_1.WithinLastUnit));
108
+ /**
109
+ * Coerce a `within_last` value — `{ n, unit }`, the only object-shaped value in
110
+ * the grammar.
111
+ *
112
+ * The bound on `n` is not decoration. `now() - make_interval(days => 1e9)`
113
+ * overflows Postgres' timestamp range and raises a `22008` the caller sees as a
114
+ * 500; a client asking for a window that long means to say "all of it", and
115
+ * should be told so with a 400 rather than a stack trace. Ten years of minutes
116
+ * is far past any real window and comfortably inside what a timestamp can hold.
117
+ */
118
+ const MAX_WITHIN_LAST_N = 10_000_000;
119
+ function withinLastValue(raw, pathStr) {
120
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
121
+ throw new filter_exceptions_1.FilterException(filter_exceptions_1.FilterErrorCode.InvalidValue, 'within_last expects { n, unit }', { path: pathStr, op: types_1.FilterOperator.WithinLast });
122
+ }
123
+ const value = raw;
124
+ const n = Number(value['n']);
125
+ const unit = value['unit'];
126
+ if (!Number.isInteger(n) || n < 1 || n > MAX_WITHIN_LAST_N) {
127
+ throw new filter_exceptions_1.FilterException(filter_exceptions_1.FilterErrorCode.InvalidValue, `within_last n must be an integer between 1 and ${MAX_WITHIN_LAST_N}`, { path: pathStr, op: types_1.FilterOperator.WithinLast, value: value['n'] });
128
+ }
129
+ if (typeof unit !== 'string' || !WITHIN_LAST_UNITS.has(unit)) {
130
+ throw new filter_exceptions_1.FilterException(filter_exceptions_1.FilterErrorCode.InvalidValue, `within_last unit must be one of ${[...WITHIN_LAST_UNITS].join(', ')}`, { path: pathStr, op: types_1.FilterOperator.WithinLast, value: unit });
131
+ }
132
+ return { n, unit: unit };
133
+ }
103
134
  function scalarOf(v, field, pathStr) {
104
135
  // A filter value has to be a scalar. `String(v)` on anything else produces
105
136
  // a plausible-looking string that is then MATCHED AGAINST rather than
@@ -1 +1 @@
1
- {"version":3,"file":"scalar-op.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/scalar-op.ts"],"names":[],"mappings":"AAAA,OAAO,EAeH,KAAK,SAAS,EACd,KAAK,GAAG,EACX,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAiBzC;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAClB,GAAG,EAAE,SAAS,EACd,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,OAAO,GACf,GAAG,CAqCL"}
1
+ {"version":3,"file":"scalar-op.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/scalar-op.ts"],"names":[],"mappings":"AAAA,OAAO,EAgBH,KAAK,SAAS,EACd,KAAK,GAAG,EACX,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAkB,MAAM,SAAS,CAAC;AAkBzD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAClB,GAAG,EAAE,SAAS,EACd,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,OAAO,GACf,GAAG,CAuCL"}
@@ -54,6 +54,8 @@ function scalar(col, op, value) {
54
54
  return negative(col, (0, drizzle_orm_1.notIlike)(col, String(value)));
55
55
  case types_1.FilterOperator.Null:
56
56
  return value === true ? (0, drizzle_orm_1.isNull)(col) : (0, drizzle_orm_1.isNotNull)(col);
57
+ case types_1.FilterOperator.WithinLast:
58
+ return withinLast(col, value);
57
59
  default:
58
60
  // The parser validates `op` against the vocabulary, so this is
59
61
  // unreachable — but falling out of the switch returned `undefined`,
@@ -64,3 +66,27 @@ function scalar(col, op, value) {
64
66
  throw new filter_exceptions_1.FilterSchemaException(`no translation for operator "${String(op)}"`);
65
67
  }
66
68
  }
69
+ /**
70
+ * `column >= now() - <n> <unit>`.
71
+ *
72
+ * The cutoff is computed by **Postgres, at query time** — which is the whole
73
+ * point of the operator surviving to the server. A cutoff computed here in
74
+ * JavaScript would be identical for one request and wrong for a stored filter
75
+ * replayed a month later.
76
+ *
77
+ * A `switch` over the three units rather than interpolating the unit into the
78
+ * SQL: `make_interval`'s argument names cannot be parameterised, so building
79
+ * the fragment from a variable would mean `sql.raw` on a value that came off
80
+ * the wire. The parser already restricts the unit to these three, and this way
81
+ * there is no path by which that could stop being true.
82
+ */
83
+ function withinLast(col, window) {
84
+ switch (window.unit) {
85
+ case types_1.WithinLastUnit.Minutes:
86
+ return (0, drizzle_orm_1.gte)(col, (0, drizzle_orm_1.sql) `now() - make_interval(mins => ${window.n})`);
87
+ case types_1.WithinLastUnit.Hours:
88
+ return (0, drizzle_orm_1.gte)(col, (0, drizzle_orm_1.sql) `now() - make_interval(hours => ${window.n})`);
89
+ default:
90
+ return (0, drizzle_orm_1.gte)(col, (0, drizzle_orm_1.sql) `now() - make_interval(days => ${window.n})`);
91
+ }
92
+ }
@@ -25,6 +25,20 @@ export declare const FilterOperator: {
25
25
  readonly Ilike: "ilike";
26
26
  readonly Nilike: "nilike";
27
27
  readonly Null: "null";
28
+ /**
29
+ * `column >= now() - <n> <unit>` — a window measured from **query time**,
30
+ * not from the moment the filter was written.
31
+ *
32
+ * The distinction is invisible in a URL and decisive in a stored one. The
33
+ * admin's query builder resolves its own `within_last` into a concrete
34
+ * `gte` cutoff when it serialises a filter into a link, deliberately: a
35
+ * shared deep link should keep showing the same rows. A filter that is
36
+ * *stored and replayed* — an alarm rule — must mean the opposite, or
37
+ * "not updated in 90 days" silently becomes "not updated since the day the
38
+ * rule was written". So the operator survives to the server, and the caller
39
+ * chooses which meaning it wants.
40
+ */
41
+ readonly WithinLast: "within_last";
28
42
  };
29
43
  /** Standard REST operator names. Translator maps each to a Drizzle helper. */
30
44
  export type FilterOperator = (typeof FilterOperator)[keyof typeof FilterOperator];
@@ -189,6 +203,21 @@ export interface FilterSchema {
189
203
  */
190
204
  maxInListLength?: number;
191
205
  }
206
+ /** Time units a {@link FilterOperator.WithinLast} window may be measured in. */
207
+ export declare const WithinLastUnit: {
208
+ readonly Minutes: "minutes";
209
+ readonly Hours: "hours";
210
+ readonly Days: "days";
211
+ };
212
+ /** One of the {@link WithinLastUnit} values. */
213
+ export type WithinLastUnit = (typeof WithinLastUnit)[keyof typeof WithinLastUnit];
214
+ /** The coerced value of a `within_last` leaf. */
215
+ export interface WithinLastValue {
216
+ /** How many units back the window reaches. A positive integer. */
217
+ n: number;
218
+ /** The unit `n` is counted in. */
219
+ unit: WithinLastUnit;
220
+ }
192
221
  /** Parser output, one node per URL filter entry. */
193
222
  export interface ParsedFilter {
194
223
  /** Dotted path split into segments, e.g. `['workspaces','name']`. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RelationKind = exports.ScalarFieldType = exports.FilterOperator = void 0;
3
+ exports.WithinLastUnit = exports.RelationKind = exports.ScalarFieldType = exports.FilterOperator = void 0;
4
4
  /** Named constants for `FilterOperator` — use in switches and comparisons. */
5
5
  exports.FilterOperator = {
6
6
  Eq: 'eq',
@@ -14,7 +14,21 @@ exports.FilterOperator = {
14
14
  Like: 'like',
15
15
  Ilike: 'ilike',
16
16
  Nilike: 'nilike',
17
- Null: 'null'
17
+ Null: 'null',
18
+ /**
19
+ * `column >= now() - <n> <unit>` — a window measured from **query time**,
20
+ * not from the moment the filter was written.
21
+ *
22
+ * The distinction is invisible in a URL and decisive in a stored one. The
23
+ * admin's query builder resolves its own `within_last` into a concrete
24
+ * `gte` cutoff when it serialises a filter into a link, deliberately: a
25
+ * shared deep link should keep showing the same rows. A filter that is
26
+ * *stored and replayed* — an alarm rule — must mean the opposite, or
27
+ * "not updated in 90 days" silently becomes "not updated since the day the
28
+ * rule was written". So the operator survives to the server, and the caller
29
+ * chooses which meaning it wants.
30
+ */
31
+ WithinLast: 'within_last'
18
32
  };
19
33
  /** Named constants for `ScalarFieldType` — use in switches. */
20
34
  exports.ScalarFieldType = {
@@ -33,3 +47,9 @@ exports.RelationKind = {
33
47
  ManyToMany: 'many-to-many',
34
48
  SelfReferential: 'self-referential'
35
49
  };
50
+ /** Time units a {@link FilterOperator.WithinLast} window may be measured in. */
51
+ exports.WithinLastUnit = {
52
+ Minutes: 'minutes',
53
+ Hours: 'hours',
54
+ Days: 'days'
55
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orthacms/utils-server",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "@orthacms/utils-server — part of Ortha CMS.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ortha-source/ortha-cms/tree/main/packages/utils/server",