@prisma-next/extension-postgis 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +283 -0
  2. package/dist/codec-types-DODPC0GY.d.mts +59 -0
  3. package/dist/codec-types-DODPC0GY.d.mts.map +1 -0
  4. package/dist/codec-types.d.mts +2 -0
  5. package/dist/codec-types.mjs +1 -0
  6. package/dist/column-types.d.mts +26 -0
  7. package/dist/column-types.d.mts.map +1 -0
  8. package/dist/column-types.mjs +28 -0
  9. package/dist/column-types.mjs.map +1 -0
  10. package/dist/constants-dLvIWSgv.mjs +6 -0
  11. package/dist/constants-dLvIWSgv.mjs.map +1 -0
  12. package/dist/control.d.mts +7 -0
  13. package/dist/control.d.mts.map +1 -0
  14. package/dist/control.mjs +189 -0
  15. package/dist/control.mjs.map +1 -0
  16. package/dist/descriptor-meta-DUKzIH9c.mjs +516 -0
  17. package/dist/descriptor-meta-DUKzIH9c.mjs.map +1 -0
  18. package/dist/geojson-Cj4ldHQ0.d.mts +61 -0
  19. package/dist/geojson-Cj4ldHQ0.d.mts.map +1 -0
  20. package/dist/geojson.d.mts +2 -0
  21. package/dist/geojson.mjs +60 -0
  22. package/dist/geojson.mjs.map +1 -0
  23. package/dist/operation-types-C2s9tY0P.d.mts +123 -0
  24. package/dist/operation-types-C2s9tY0P.d.mts.map +1 -0
  25. package/dist/operation-types.d.mts +2 -0
  26. package/dist/operation-types.mjs +1 -0
  27. package/dist/pack.d.mts +75 -0
  28. package/dist/pack.d.mts.map +1 -0
  29. package/dist/pack.mjs +2 -0
  30. package/dist/runtime.d.mts +19 -0
  31. package/dist/runtime.d.mts.map +1 -0
  32. package/dist/runtime.mjs +22 -0
  33. package/dist/runtime.mjs.map +1 -0
  34. package/package.json +64 -0
  35. package/src/contract.d.ts +91 -0
  36. package/src/contract.json +40 -0
  37. package/src/contract.ts +61 -0
  38. package/src/core/authoring.ts +18 -0
  39. package/src/core/codecs.ts +193 -0
  40. package/src/core/constants.ts +3 -0
  41. package/src/core/contract-space-constants.ts +30 -0
  42. package/src/core/descriptor-meta.ts +186 -0
  43. package/src/core/ewkb.ts +284 -0
  44. package/src/core/geojson.ts +131 -0
  45. package/src/core/registry.ts +14 -0
  46. package/src/exports/codec-types.ts +7 -0
  47. package/src/exports/column-types.ts +38 -0
  48. package/src/exports/control.ts +101 -0
  49. package/src/exports/geojson.ts +19 -0
  50. package/src/exports/operation-types.ts +7 -0
  51. package/src/exports/pack.ts +1 -0
  52. package/src/exports/runtime.ts +34 -0
  53. package/src/types/codec-types.ts +16 -0
  54. package/src/types/operation-types.ts +81 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * GeoJSON-shaped value types used by the PostGIS codec.
3
+ *
4
+ * The codec speaks GeoJSON-style objects to JavaScript callers and
5
+ * EWKT/EWKB on the wire. This file is the single source of truth for the
6
+ * value shapes; the public type re-export lives in `exports/geojson.ts`.
7
+ */
8
+
9
+ export type Position = readonly [number, number];
10
+
11
+ export type GeometryPoint = {
12
+ readonly type: 'Point';
13
+ readonly coordinates: Position;
14
+ readonly srid?: number;
15
+ };
16
+
17
+ export type GeometryLineString = {
18
+ readonly type: 'LineString';
19
+ readonly coordinates: ReadonlyArray<Position>;
20
+ readonly srid?: number;
21
+ };
22
+
23
+ export type GeometryPolygon = {
24
+ readonly type: 'Polygon';
25
+ readonly coordinates: ReadonlyArray<ReadonlyArray<Position>>;
26
+ readonly srid?: number;
27
+ };
28
+
29
+ export type GeometryMultiPoint = {
30
+ readonly type: 'MultiPoint';
31
+ readonly coordinates: ReadonlyArray<Position>;
32
+ readonly srid?: number;
33
+ };
34
+
35
+ export type GeometryMultiLineString = {
36
+ readonly type: 'MultiLineString';
37
+ readonly coordinates: ReadonlyArray<ReadonlyArray<Position>>;
38
+ readonly srid?: number;
39
+ };
40
+
41
+ export type GeometryMultiPolygon = {
42
+ readonly type: 'MultiPolygon';
43
+ readonly coordinates: ReadonlyArray<ReadonlyArray<ReadonlyArray<Position>>>;
44
+ readonly srid?: number;
45
+ };
46
+
47
+ export type Geometry =
48
+ | GeometryPoint
49
+ | GeometryLineString
50
+ | GeometryPolygon
51
+ | GeometryMultiPoint
52
+ | GeometryMultiLineString
53
+ | GeometryMultiPolygon;
54
+
55
+ /**
56
+ * Construct a Point. Convenience for the common "lng/lat" case.
57
+ *
58
+ * @example
59
+ * point(-122.4194, 37.7749, 4326)
60
+ */
61
+ export function point(longitude: number, latitude: number, srid?: number): GeometryPoint {
62
+ if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
63
+ throw new RangeError('point: coordinates must be finite numbers');
64
+ }
65
+ return srid !== undefined
66
+ ? { type: 'Point', coordinates: [longitude, latitude], srid }
67
+ : { type: 'Point', coordinates: [longitude, latitude] };
68
+ }
69
+
70
+ /**
71
+ * Construct a Polygon from a single outer ring of `[lng, lat]` pairs.
72
+ * If the first and last positions differ, the ring is closed automatically.
73
+ */
74
+ export function polygon(ring: ReadonlyArray<Position>, srid?: number): GeometryPolygon {
75
+ if (ring.length < 3) {
76
+ throw new Error('polygon: ring must contain at least 3 positions');
77
+ }
78
+ const first = ring[0];
79
+ const last = ring[ring.length - 1];
80
+ if (!first || !last) {
81
+ throw new Error('polygon: ring positions cannot be undefined');
82
+ }
83
+ for (const position of ring) {
84
+ if (!Number.isFinite(position[0]) || !Number.isFinite(position[1])) {
85
+ throw new RangeError('polygon: coordinates must be finite numbers');
86
+ }
87
+ }
88
+ const closed = first[0] === last[0] && first[1] === last[1] ? ring : [...ring, first];
89
+ const distinct = new Set(closed.slice(0, -1).map(([x, y]) => `${x},${y}`));
90
+ if (distinct.size < 3) {
91
+ throw new Error('polygon: ring must contain at least 3 distinct positions');
92
+ }
93
+ return srid !== undefined
94
+ ? { type: 'Polygon', coordinates: [closed], srid }
95
+ : { type: 'Polygon', coordinates: [closed] };
96
+ }
97
+
98
+ /**
99
+ * Construct a rectangular Polygon from a bounding box.
100
+ *
101
+ * @param bbox - `[minLng, minLat, maxLng, maxLat]`
102
+ */
103
+ export function bboxPolygon(
104
+ bbox: readonly [number, number, number, number],
105
+ srid?: number,
106
+ ): GeometryPolygon {
107
+ const [minX, minY, maxX, maxY] = bbox;
108
+ if (
109
+ !Number.isFinite(minX) ||
110
+ !Number.isFinite(minY) ||
111
+ !Number.isFinite(maxX) ||
112
+ !Number.isFinite(maxY)
113
+ ) {
114
+ throw new RangeError('bboxPolygon: coordinates must be finite numbers');
115
+ }
116
+ if (minX > maxX || minY > maxY) {
117
+ throw new Error(
118
+ `bboxPolygon: inverted bbox [${minX}, ${minY}, ${maxX}, ${maxY}] (expected minX <= maxX and minY <= maxY)`,
119
+ );
120
+ }
121
+ return polygon(
122
+ [
123
+ [minX, minY],
124
+ [maxX, minY],
125
+ [maxX, maxY],
126
+ [minX, maxY],
127
+ [minX, minY],
128
+ ],
129
+ srid,
130
+ );
131
+ }
@@ -0,0 +1,14 @@
1
+ import { buildCodecDescriptorRegistry } from '@prisma-next/sql-relational-core/codec-descriptor-registry';
2
+ import type { CodecDescriptorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
3
+ import { codecDescriptors } from './codecs';
4
+
5
+ /**
6
+ * Registry of every codec descriptor shipped by `@prisma-next/extension-postgis`.
7
+ *
8
+ * Public consumer surface for the postgis codec set. Currently a single
9
+ * entry (`pg/geometry@1`); the registry shape stays consistent with
10
+ * the other codec-shipping packages so consumers don't need to
11
+ * special-case extensions.
12
+ */
13
+ export const postgisCodecRegistry: CodecDescriptorRegistry =
14
+ buildCodecDescriptorRegistry(codecDescriptors);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Codec type definitions for the PostGIS extension.
3
+ *
4
+ * Re-export from types module for public API.
5
+ */
6
+
7
+ export type { CodecTypes, Geometry } from '../types/codec-types';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Column type descriptors for the PostGIS extension.
3
+ *
4
+ * Use `geometryColumn` for an untyped `geometry` column, or
5
+ * `geometry({ srid })` to declare an SRID-constrained column whose DDL
6
+ * comes out as `geometry(Geometry, <srid>)`.
7
+ */
8
+
9
+ import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
10
+ import { POSTGIS_GEOMETRY_CODEC_ID } from '../core/constants';
11
+
12
+ export const geometryColumn = {
13
+ codecId: POSTGIS_GEOMETRY_CODEC_ID,
14
+ nativeType: 'geometry',
15
+ } as const satisfies ColumnTypeDescriptor;
16
+
17
+ /**
18
+ * Build an SRID-constrained geometry column descriptor.
19
+ *
20
+ * @example
21
+ * .column('location', { type: geometry({ srid: 4326 }), nullable: false })
22
+ * // Produces: nativeType: 'geometry', typeParams: { srid: 4326 }
23
+ *
24
+ * @throws {RangeError} If `srid` is not a non-negative integer.
25
+ */
26
+ export function geometry<S extends number>(options: {
27
+ readonly srid: S;
28
+ }): ColumnTypeDescriptor & { readonly typeParams: { readonly srid: S } } {
29
+ const { srid } = options;
30
+ if (!Number.isInteger(srid) || srid < 0) {
31
+ throw new RangeError(`postgis: srid must be a non-negative integer, got ${srid}`);
32
+ }
33
+ return {
34
+ codecId: POSTGIS_GEOMETRY_CODEC_ID,
35
+ nativeType: 'geometry',
36
+ typeParams: { srid },
37
+ } as const;
38
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Control-plane descriptor for the postgis extension.
3
+ *
4
+ * **Contract-space package layout.** The extension's contract
5
+ * + migrations are emitted by the same pipeline application authors use:
6
+ *
7
+ * `prisma-next contract emit` → `<package>/src/contract.{json,d.ts}`
8
+ * `prisma-next migration plan` → `<package>/migrations/<dir>/...`
9
+ *
10
+ * The descriptor wires those JSON artefacts via JSON-import declarations
11
+ * so they flow through the consuming application's module resolver
12
+ * without filesystem assumptions, and synthesises the canonical
13
+ * `MigrationPackage` shape for the framework's runner / verifier to
14
+ * consume.
15
+ *
16
+ * Wired surfaces:
17
+ *
18
+ * - `contractSpace.{contractJson,migrations,headRef}` — sourced from
19
+ * the on-disk artefacts emitted by `build:contract-space`.
20
+ * - `types.codecTypes.controlPlaneHooks[POSTGIS_GEOMETRY_CODEC_ID]` —
21
+ * codec control hooks (`expandNativeType`, `resolveIdentityValue`)
22
+ * the SQL planner extracts via `extractCodecControlHooks` and uses
23
+ * to render `geometry(Geometry,${srid})` column types.
24
+ *
25
+ * @see docs/architecture docs/adrs/ADR 212 - Contract spaces.md
26
+ * (contract-space package layout convention).
27
+ */
28
+
29
+ import type { Contract } from '@prisma-next/contract/types';
30
+ import type {
31
+ CodecControlHooks,
32
+ SqlControlExtensionDescriptor,
33
+ } from '@prisma-next/family-sql/control';
34
+ import { contractSpaceFromJson } from '@prisma-next/migration-tools/spaces';
35
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
36
+ import baselineMetadata from '../../migrations/20260601T0000_install_postgis_extension/migration.json' with {
37
+ type: 'json',
38
+ };
39
+ import baselineOps from '../../migrations/20260601T0000_install_postgis_extension/ops.json' with {
40
+ type: 'json',
41
+ };
42
+ import headRef from '../../migrations/refs/head.json' with { type: 'json' };
43
+ import contractJson from '../contract.json' with { type: 'json' };
44
+ import { POSTGIS_GEOMETRY_CODEC_ID } from '../core/constants';
45
+ import {
46
+ POSTGIS_BASELINE_MIGRATION_NAME,
47
+ POSTGIS_SPACE_ID,
48
+ } from '../core/contract-space-constants';
49
+ import { postgisPackMeta, postgisQueryOperations } from '../core/descriptor-meta';
50
+
51
+ const geometryControlPlaneHooks: CodecControlHooks = {
52
+ expandNativeType: ({ nativeType, typeParams }) => {
53
+ const srid = typeParams?.['srid'];
54
+ if (typeof srid === 'number' && Number.isInteger(srid) && srid >= 0) {
55
+ // PostGIS prints the type-modifier list without a space — match
56
+ // it here so the verifier doesn't see `geometry(Geometry, 4326)`
57
+ // (DDL) mismatch `geometry(Geometry,4326)` (introspected).
58
+ return `${nativeType}(Geometry,${srid})`;
59
+ }
60
+ return nativeType;
61
+ },
62
+ // PostGIS has no canonical "identity" geometry; backfilling a
63
+ // non-null column requires the user to supply a valid value, so we
64
+ // don't synthesise one here.
65
+ resolveIdentityValue: () => null,
66
+ };
67
+
68
+ const postgisContractSpace = contractSpaceFromJson<Contract<SqlStorage>>({
69
+ contractJson,
70
+ migrations: [
71
+ {
72
+ dirName: POSTGIS_BASELINE_MIGRATION_NAME,
73
+ metadata: baselineMetadata,
74
+ ops: baselineOps,
75
+ },
76
+ ],
77
+ headRef,
78
+ });
79
+
80
+ const postgisExtensionDescriptor: SqlControlExtensionDescriptor<'postgres'> = {
81
+ ...postgisPackMeta,
82
+ id: POSTGIS_SPACE_ID,
83
+ contractSpace: postgisContractSpace,
84
+ types: {
85
+ ...postgisPackMeta.types,
86
+ codecTypes: {
87
+ ...postgisPackMeta.types.codecTypes,
88
+ controlPlaneHooks: {
89
+ [POSTGIS_GEOMETRY_CODEC_ID]: geometryControlPlaneHooks,
90
+ },
91
+ },
92
+ },
93
+ queryOperations: () => postgisQueryOperations(),
94
+ create: () => ({
95
+ familyId: 'sql' as const,
96
+ targetId: 'postgres' as const,
97
+ }),
98
+ };
99
+
100
+ export { postgisExtensionDescriptor };
101
+ export default postgisExtensionDescriptor;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * GeoJSON-shaped value types and constructors used by the PostGIS codec.
3
+ *
4
+ * Surface these so callers can produce well-formed point/polygon values
5
+ * without depending on internal modules.
6
+ */
7
+
8
+ export type {
9
+ Geometry,
10
+ GeometryLineString,
11
+ GeometryMultiLineString,
12
+ GeometryMultiPoint,
13
+ GeometryMultiPolygon,
14
+ GeometryPoint,
15
+ GeometryPolygon,
16
+ Position,
17
+ } from '../core/geojson';
18
+
19
+ export { bboxPolygon, point, polygon } from '../core/geojson';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Operation type definitions for the PostGIS extension.
3
+ *
4
+ * Re-export from types module for public API.
5
+ */
6
+
7
+ export type { OperationTypes, QueryOperationTypes } from '../types/operation-types';
@@ -0,0 +1 @@
1
+ export { postgisPackMeta as default } from '../core/descriptor-meta';
@@ -0,0 +1,34 @@
1
+ import type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';
2
+ import { postgisPackMeta, postgisQueryOperations } from '../core/descriptor-meta';
3
+ import { postgisCodecRegistry } from '../core/registry';
4
+
5
+ const postgisRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {
6
+ kind: 'extension' as const,
7
+ id: postgisPackMeta.id,
8
+ version: postgisPackMeta.version,
9
+ familyId: 'sql' as const,
10
+ targetId: 'postgres' as const,
11
+ // Expose the unified descriptor list so `extractCodecLookup` reads
12
+ // `targetTypes` / `meta` / `renderOutputType` directly off the
13
+ // descriptors and materialises the representative `Codec` for the
14
+ // SQL renderer's cast-policy lookup. Without it, the Postgres
15
+ // adapter's runtime codec lookup would miss `pg/geometry@1` and
16
+ // `$N::geometry` casts would disappear once the renderer switches
17
+ // to lookup-driven cast policy.
18
+ types: {
19
+ codecTypes: {
20
+ codecDescriptors: Array.from(postgisCodecRegistry.values()),
21
+ },
22
+ },
23
+ codecs: () => Array.from(postgisCodecRegistry.values()),
24
+ queryOperations: () => postgisQueryOperations(),
25
+ create() {
26
+ return {
27
+ familyId: 'sql' as const,
28
+ targetId: 'postgres' as const,
29
+ };
30
+ },
31
+ };
32
+
33
+ export { postgisCodecRegistry };
34
+ export default postgisRuntimeDescriptor;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Codec type definitions for the PostGIS extension.
3
+ *
4
+ * The runtime value carried for a `pg/geometry@1` cell is a GeoJSON-shaped
5
+ * object — see `core/geojson.ts` for the variant union. The `Geometry`
6
+ * type re-exported here is what `contract.d.ts` will reference; the
7
+ * optional generic `<S>` carries SRID metadata at the type level for
8
+ * dimensioned columns (e.g. `Geometry<4326>`).
9
+ */
10
+
11
+ import type { CodecTypes as CoreCodecTypes } from '../core/codecs';
12
+ import type { Geometry as GeometryValue } from '../core/geojson';
13
+
14
+ export type Geometry<_Srid extends number = number> = GeometryValue;
15
+
16
+ export type CodecTypes = CoreCodecTypes;
@@ -0,0 +1,81 @@
1
+ import type { SqlQueryOperationTypes } from '@prisma-next/sql-contract/types';
2
+ import type { CodecExpression, Expression } from '@prisma-next/sql-relational-core/expression';
3
+
4
+ type CodecTypesBase = Record<string, { readonly input: unknown; readonly output: unknown }>;
5
+
6
+ /**
7
+ * Operation type definitions for the PostGIS extension.
8
+ *
9
+ * These are type-only signatures consumed by emitted `contract.d.ts`
10
+ * files so the query builder surfaces `.distance()`, `.dwithin()`,
11
+ * `.contains()`, `.within()`, `.intersects()`, and `.intersectsBbox()`
12
+ * on `geometry` columns.
13
+ */
14
+
15
+ export type OperationTypes = {
16
+ readonly 'pg/geometry@1': {
17
+ readonly distance: { readonly self: { readonly codecId: 'pg/geometry@1' } };
18
+ readonly distanceSphere: { readonly self: { readonly codecId: 'pg/geometry@1' } };
19
+ readonly dwithin: { readonly self: { readonly codecId: 'pg/geometry@1' } };
20
+ readonly contains: { readonly self: { readonly codecId: 'pg/geometry@1' } };
21
+ readonly within: { readonly self: { readonly codecId: 'pg/geometry@1' } };
22
+ readonly intersects: { readonly self: { readonly codecId: 'pg/geometry@1' } };
23
+ readonly intersectsBbox: { readonly self: { readonly codecId: 'pg/geometry@1' } };
24
+ };
25
+ };
26
+
27
+ export type QueryOperationTypes<CT extends CodecTypesBase> = SqlQueryOperationTypes<
28
+ CT,
29
+ {
30
+ readonly distance: {
31
+ readonly self: { readonly codecId: 'pg/geometry@1' };
32
+ readonly impl: (
33
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
34
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
35
+ ) => Expression<{ codecId: 'pg/float8@1'; nullable: false }>;
36
+ };
37
+ readonly distanceSphere: {
38
+ readonly self: { readonly codecId: 'pg/geometry@1' };
39
+ readonly impl: (
40
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
41
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
42
+ ) => Expression<{ codecId: 'pg/float8@1'; nullable: false }>;
43
+ };
44
+ readonly dwithin: {
45
+ readonly self: { readonly codecId: 'pg/geometry@1' };
46
+ readonly impl: (
47
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
48
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
49
+ distance: CodecExpression<'pg/float8@1', boolean, CT>,
50
+ ) => Expression<{ codecId: 'pg/bool@1'; nullable: false }>;
51
+ };
52
+ readonly contains: {
53
+ readonly self: { readonly codecId: 'pg/geometry@1' };
54
+ readonly impl: (
55
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
56
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
57
+ ) => Expression<{ codecId: 'pg/bool@1'; nullable: false }>;
58
+ };
59
+ readonly within: {
60
+ readonly self: { readonly codecId: 'pg/geometry@1' };
61
+ readonly impl: (
62
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
63
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
64
+ ) => Expression<{ codecId: 'pg/bool@1'; nullable: false }>;
65
+ };
66
+ readonly intersects: {
67
+ readonly self: { readonly codecId: 'pg/geometry@1' };
68
+ readonly impl: (
69
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
70
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
71
+ ) => Expression<{ codecId: 'pg/bool@1'; nullable: false }>;
72
+ };
73
+ readonly intersectsBbox: {
74
+ readonly self: { readonly codecId: 'pg/geometry@1' };
75
+ readonly impl: (
76
+ self: CodecExpression<'pg/geometry@1', boolean, CT>,
77
+ other: CodecExpression<'pg/geometry@1', boolean, CT>,
78
+ ) => Expression<{ codecId: 'pg/bool@1'; nullable: false }>;
79
+ };
80
+ }
81
+ >;