kern-orm 0.0.0-stage → 0.2.0

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/types.mjs ADDED
@@ -0,0 +1,254 @@
1
+ import { C as equalityOperators, a as stringifyJson, b as defineType, i as formatTypeName, l as orderingOperators, n as text, o as decodeBytea, r as varchar, s as encodeBytea, t as char, w as hasEquality, y as getBooleanType } from "./text-4Xs09pIf.mjs";
2
+ // src/type/builtins/array.ts
3
+ /**
4
+ * Creates a one-dimensional array type of `element` values.
5
+ *
6
+ * A `NULL` element decodes to `null`; a nullable `element` type, such as `array(text.nullable())`, types such elements.
7
+ * Elements are separated by commas, as in arrays of all built-in types; a custom type with another delimiter, such as `box`, is not supported.
8
+ * An array has `eq`, `ne`, and `in` when its element type has SQL `=`.
9
+ */
10
+ function array(element) {
11
+ const type = defineType({
12
+ sqlType: `${element.sqlType}[]`,
13
+ castType: `${element.castType}[]`
14
+ }).transform({
15
+ decode(text) {
16
+ const values = parseArray(text);
17
+ for (let index = 0; index < values.length; index++) {
18
+ const value = values[index];
19
+ values[index] = value === null ? null : element.decode(value);
20
+ }
21
+ return values;
22
+ },
23
+ encode(values) {
24
+ let text = "{";
25
+ for (let index = 0; index < values.length; index++) {
26
+ const value = values[index];
27
+ text += `${index === 0 ? "" : ","}${formatArrayElement(value === null ? null : element.encode(value))}`;
28
+ }
29
+ return `${text}}`;
30
+ }
31
+ });
32
+ return hasEquality(element) ? type.withOperators(equalityOperators) : type;
33
+ }
34
+ /** Parses a one-dimensional array literal, such as `{a,"b c",NULL}`, into the text representations of its elements. */
35
+ function parseArray(text) {
36
+ const start = text.startsWith("[") ? text.indexOf("=") + 1 : 0;
37
+ if (text[start] !== "{" || !text.endsWith("}")) throw new Error(`Unsupported array value "${text}"`);
38
+ const elements = [];
39
+ const end = text.length - 1;
40
+ if (start + 1 === end) return elements;
41
+ let index = start + 1;
42
+ while (true) {
43
+ if (text[index] === "{") throw new Error("Multidimensional arrays are not supported");
44
+ if (text[index] === "\"") {
45
+ let element = "";
46
+ index++;
47
+ while (index < end && text[index] !== "\"") {
48
+ if (text[index] === "\\") {
49
+ index++;
50
+ if (index === end) break;
51
+ }
52
+ element += text[index];
53
+ index++;
54
+ }
55
+ if (index === end) throw new Error(`Unterminated quoted element in array value "${text}"`);
56
+ elements.push(element);
57
+ index++;
58
+ } else {
59
+ let next = text.indexOf(",", index);
60
+ if (next === -1) next = end;
61
+ if (next === index) throw new Error(`Malformed array value "${text}"`);
62
+ const element = text.slice(index, next);
63
+ elements.push(element === "NULL" ? null : element);
64
+ index = next;
65
+ }
66
+ if (index === end) return elements;
67
+ if (text[index] !== ",") throw new Error(`Malformed array value "${text}"`);
68
+ index++;
69
+ }
70
+ }
71
+ /** Formats the text representation of an array element, quoting it unless it is `NULL`. */
72
+ function formatArrayElement(element) {
73
+ return element === null ? "NULL" : `"${element.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
74
+ }
75
+ // src/type/builtins/binary.ts
76
+ /** Binary data; PostgreSQL represents it in hex, which requires the default `bytea_output` setting. */
77
+ const bytea = /* @__PURE__ */ defineType({ sqlType: "bytea" }).transform({
78
+ decode: decodeBytea,
79
+ encode: encodeBytea
80
+ }).withOperators(equalityOperators);
81
+ // src/type/builtins/boolean.ts
82
+ /** `boolean` values. */
83
+ const boolean = /* @__PURE__ */ getBooleanType();
84
+ // src/type/builtins/datetime.ts
85
+ /** Point in time as a UTC ISO 8601 string with microseconds, such as `2026-01-15T07:00:00.123456Z`; accepts a `Date` too. */
86
+ const timestamptz = /* @__PURE__ */ defineType({ sqlType: "timestamptz" }).transform({
87
+ decode: normalizeTimestamptz,
88
+ encode: (value) => toPostgresYear(value instanceof Date ? value.toISOString() : value)
89
+ }).withOperators(orderingOperators).withParameters(/* @__PURE__ */ createPrecisionFormatter("timestamptz"));
90
+ /** Date and time without a time zone as an ISO 8601 string with microseconds, such as `2026-01-15T10:00:00.000000`. */
91
+ const timestamp = /* @__PURE__ */ defineType({ sqlType: "timestamp" }).transform({
92
+ decode: normalizeTimestamp,
93
+ encode: toPostgresYear
94
+ }).withOperators(orderingOperators).withParameters(/* @__PURE__ */ createPrecisionFormatter("timestamp"));
95
+ /** Calendar date as an ISO 8601 string, such as `2026-01-15`. */
96
+ const date = /* @__PURE__ */ defineType({ sqlType: "date" }).transform({
97
+ decode: normalizeDate,
98
+ encode: toPostgresYear
99
+ }).withOperators(orderingOperators);
100
+ /** Time of day as a string with microseconds, such as `10:00:00.000000`. */
101
+ const time = /* @__PURE__ */ defineType({ sqlType: "time" }).transform({ decode: normalizeTime }).withOperators(orderingOperators).withParameters(/* @__PURE__ */ createPrecisionFormatter("time"));
102
+ /** Interval in the PostgreSQL output format, such as `1 day 02:00:00`. */
103
+ const interval = /* @__PURE__ */ defineType({ sqlType: "interval" }).withOperators(orderingOperators);
104
+ function createPrecisionFormatter(name) {
105
+ return (precision) => formatTypeName(name, precision);
106
+ }
107
+ const timestamptzPattern = /^(\d{4,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?([+-])(\d{2})(?::(\d{2}))?(?::(\d{2}))?( BC)?$/;
108
+ const timestampPattern = /^(\d{4,})(-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})(?:\.(\d{1,6}))?( BC)?$/;
109
+ const datePattern = /^(\d{4,})(-\d{2}-\d{2})( BC)?$/;
110
+ const timePattern = /^(\d{2}:\d{2}:\d{2})(?:\.(\d{1,6}))?$/;
111
+ /** Leading ISO 8601 year that PostgreSQL does not accept: an expanded year, such as `+010000`, or year `0000`. */
112
+ const isoYearPattern = /^([+-]\d{6,}|0000)(-.*)$/s;
113
+ /** Special date and time values that PostgreSQL returns as they are. */
114
+ const infinities = /* @__PURE__ */ new Set(["infinity", "-infinity"]);
115
+ /**
116
+ * Normalizes a `timestamptz` value to UTC ISO 8601 with microseconds, such as `2026-01-15T07:00:00.123456Z`.
117
+ *
118
+ * The fixed format keeps microseconds that `Date` would lose; the arithmetic avoids `Date`, whose range is narrower than that of PostgreSQL.
119
+ */
120
+ function normalizeTimestamptz(text) {
121
+ if (infinities.has(text)) return text;
122
+ const match = timestamptzPattern.exec(text);
123
+ if (!match) throw new Error(`Unsupported timestamptz value "${text}"; kern-orm expects DateStyle ISO`);
124
+ const offset = (match[8] === "-" ? -1 : 1) * (Number(match[9]) * 3600 + Number(match[10] ?? 0) * 60 + Number(match[11] ?? 0));
125
+ const seconds = countDays(toAstronomicalYear(match[1], match[12]), Number(match[2]), Number(match[3])) * 86400 + Number(match[4]) * 3600 + Number(match[5]) * 60 + Number(match[6]) - offset;
126
+ const days = Math.floor(seconds / 86400);
127
+ const time = seconds - days * 86400;
128
+ return `${formatDate(days)}T${padTwo(Math.floor(time / 3600))}:${padTwo(Math.floor(time / 60) % 60)}:${padTwo(time % 60)}.${padFraction(match[7])}Z`;
129
+ }
130
+ /** Normalizes a `timestamp` value to ISO 8601 without a time zone, such as `2026-01-15T10:00:00.000000`. */
131
+ function normalizeTimestamp(text) {
132
+ if (infinities.has(text)) return text;
133
+ const match = timestampPattern.exec(text);
134
+ if (!match) throw new Error(`Unsupported timestamp value "${text}"; kern-orm expects DateStyle ISO`);
135
+ return `${formatYear(toAstronomicalYear(match[1], match[5]))}${match[2]}T${match[3]}.${padFraction(match[4])}`;
136
+ }
137
+ /** Normalizes a `date` value to ISO 8601, such as `2026-01-15`. */
138
+ function normalizeDate(text) {
139
+ if (infinities.has(text)) return text;
140
+ const match = datePattern.exec(text);
141
+ if (!match) throw new Error(`Unsupported date value "${text}"; kern-orm expects DateStyle ISO`);
142
+ return `${formatYear(toAstronomicalYear(match[1], match[3]))}${match[2]}`;
143
+ }
144
+ /** Normalizes a `time` value to `HH:MM:SS.ffffff`. */
145
+ function normalizeTime(text) {
146
+ const match = timePattern.exec(text);
147
+ if (!match) throw new Error(`Unsupported time value "${text}"`);
148
+ return `${match[1]}.${padFraction(match[2])}`;
149
+ }
150
+ /** Converts a leading ISO 8601 year outside 1 to 9999 into the PostgreSQL format: unsigned after 9999 and with a `BC` suffix before 1. */
151
+ function toPostgresYear(text) {
152
+ const match = isoYearPattern.exec(text);
153
+ if (!match) return text;
154
+ const year = Number(match[1]);
155
+ return year > 0 ? `${year}${match[2]}` : `${String(1 - year).padStart(4, "0")}${match[2]} BC`;
156
+ }
157
+ /** Returns the astronomical year of a PostgreSQL year, where 1 BC is year 0. */
158
+ function toAstronomicalYear(year, era) {
159
+ return era === void 0 ? Number(year) : 1 - Number(year);
160
+ }
161
+ /** Formats an astronomical year as an ISO 8601 year, expanded with a sign outside 0 to 9999. */
162
+ function formatYear(year) {
163
+ if (year >= 0 && year <= 9999) return String(year).padStart(4, "0");
164
+ return `${year < 0 ? "-" : "+"}${String(Math.abs(year)).padStart(6, "0")}`;
165
+ }
166
+ /** Counts the days from 1970-01-01 to a date of an astronomical year. */
167
+ function countDays(year, month, day) {
168
+ const shifted = month <= 2 ? year - 1 : year;
169
+ const era = Math.floor(shifted / 400);
170
+ const yearOfEra = shifted - era * 400;
171
+ const dayOfYear = Math.floor((153 * (month > 2 ? month - 3 : month + 9) + 2) / 5) + day - 1;
172
+ const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
173
+ return era * 146097 + dayOfEra - 719468;
174
+ }
175
+ /** Formats the date a number of days after 1970-01-01 as an ISO 8601 date. */
176
+ function formatDate(days) {
177
+ const shifted = days + 719468;
178
+ const era = Math.floor(shifted / 146097);
179
+ const dayOfEra = shifted - era * 146097;
180
+ const yearOfEra = Math.floor((dayOfEra - Math.floor(dayOfEra / 1460) + Math.floor(dayOfEra / 36524) - Math.floor(dayOfEra / 146096)) / 365);
181
+ const dayOfYear = dayOfEra - (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
182
+ const shiftedMonth = Math.floor((5 * dayOfYear + 2) / 153);
183
+ const day = dayOfYear - Math.floor((153 * shiftedMonth + 2) / 5) + 1;
184
+ const month = shiftedMonth < 10 ? shiftedMonth + 3 : shiftedMonth - 9;
185
+ return `${formatYear(yearOfEra + era * 400 + (month <= 2 ? 1 : 0))}-${padTwo(month)}-${padTwo(day)}`;
186
+ }
187
+ function padTwo(value) {
188
+ return value < 10 ? `0${value}` : String(value);
189
+ }
190
+ function padFraction(fraction) {
191
+ return (fraction ?? "").padEnd(6, "0");
192
+ }
193
+ // src/type/builtins/enum.ts
194
+ /** Creates a type of an existing PostgreSQL enum; values are ordered as declared in the enum. */
195
+ function enumOf(name, _values) {
196
+ return defineType({
197
+ sqlType: name,
198
+ castType: name
199
+ }).transform({
200
+ decode: (value) => value,
201
+ encode: (value) => value
202
+ }).withOperators(orderingOperators);
203
+ }
204
+ // src/type/builtins/json.ts
205
+ function createJsonType(sqlType) {
206
+ return defineType({ sqlType }).transform({
207
+ decode: (text) => JSON.parse(text),
208
+ encode: stringifyJson
209
+ });
210
+ }
211
+ function addShape(type) {
212
+ return Object.freeze({
213
+ ...type,
214
+ of: () => type
215
+ });
216
+ }
217
+ /** `json` values; `json.of<T>()` declares their shape. PostgreSQL has no `=` for `json`, so its expressions have no `eq`, `ne`, or `in`. */
218
+ const json = /* @__PURE__ */ addShape(/* @__PURE__ */ createJsonType("json"));
219
+ /** `jsonb` values; `jsonb.of<T>()` declares their shape. */
220
+ const jsonb = /* @__PURE__ */ addShape(/* @__PURE__ */ createJsonType("jsonb").withOperators(equalityOperators));
221
+ // src/type/builtins/network.ts
222
+ /** IP address with an optional netmask as a string, such as `192.168.0.1`. */
223
+ const inet = /* @__PURE__ */ defineType({ sqlType: "inet" }).withOperators(orderingOperators);
224
+ /** IP network as a string, such as `10.0.0.0/8`. */
225
+ const cidr = /* @__PURE__ */ defineType({ sqlType: "cidr" }).withOperators(orderingOperators);
226
+ // src/type/builtins/numeric.ts
227
+ function defineNumberType(sqlType) {
228
+ return defineType({ sqlType }).transform({
229
+ decode: Number,
230
+ encode: (value) => String(value)
231
+ }).withOperators(orderingOperators);
232
+ }
233
+ /** 2-byte integer as a `number`. */
234
+ const smallint = /* @__PURE__ */ defineNumberType("smallint");
235
+ /** 4-byte integer as a `number`. */
236
+ const integer = /* @__PURE__ */ defineNumberType("integer");
237
+ /** 8-byte integer as a `bigint`, which keeps values beyond `Number.MAX_SAFE_INTEGER`; accepts a `number` too. */
238
+ const bigint = /* @__PURE__ */ defineType({ sqlType: "bigint" }).transform({
239
+ decode: BigInt,
240
+ encode: (value) => String(value)
241
+ }).withOperators(orderingOperators);
242
+ /** 4-byte floating-point number as a `number`. */
243
+ const real = /* @__PURE__ */ defineNumberType("real");
244
+ /** 8-byte floating-point number as a `number`. */
245
+ const doublePrecision = /* @__PURE__ */ defineNumberType("double precision");
246
+ /** Exact number as a decimal string, so that no precision is lost; `numeric.with(precision, scale)` constrains it. */
247
+ const numeric = /* @__PURE__ */ defineType({ sqlType: "numeric" }).transform({
248
+ decode: (value) => value,
249
+ encode: (value) => String(value)
250
+ }).withOperators(orderingOperators).withParameters((precision, scale) => formatTypeName("numeric", precision, scale));
251
+ // src/type/builtins/uuid.ts
252
+ /** UUID as a string. */
253
+ const uuid = /* @__PURE__ */ defineType({ sqlType: "uuid" }).withOperators(orderingOperators);
254
+ export { array, bigint, boolean, bytea, char, cidr, date, doublePrecision, enumOf as enum, inet, integer, interval, json, jsonb, numeric, real, smallint, text, time, timestamp, timestamptz, uuid, varchar };
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "kern-orm",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Fully typed relational query builder for PostgreSQL with ambient transactions and single-query relation loading",
5
+ "keywords": [
6
+ "orm",
7
+ "postgres",
8
+ "postgresql",
9
+ "query-builder",
10
+ "sql",
11
+ "transactions",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://ilyasemenov.github.io/kern-orm/",
15
+ "bugs": "https://github.com/IlyaSemenov/kern-orm/issues",
16
+ "license": "MIT",
17
+ "author": "Ilya Semenov",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/IlyaSemenov/kern-orm.git",
21
+ "directory": "packages/kern-orm"
22
+ },
23
+ "files": [
24
+ "README.md",
25
+ "dist"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "types": "./dist/index.d.mts",
30
+ "exports": {
31
+ ".": "./dist/index.mjs",
32
+ "./testing": "./dist/testing.mjs",
33
+ "./types": "./dist/types.mjs",
34
+ "./package.json": "./package.json"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsdown",
41
+ "prepublishOnly": "bun run build"
42
+ },
43
+ "devDependencies": {
44
+ "@kern-orm/postgres-js": "workspace:*",
45
+ "postgres": "catalog:",
46
+ "typescript-6": "catalog:"
47
+ },
48
+ "engines": {
49
+ "bun": ">=1.4",
50
+ "node": ">=24"
51
+ }
52
+ }