neoorm 0.7.4 → 0.8.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/README.md +4 -4
- package/dist/bin/neoorm.js +36 -27
- package/dist/bin/neoorm.js.map +1 -1
- package/dist/codegen/diff-manifest.d.ts.map +1 -1
- package/dist/codegen/diff-manifest.js +55 -30
- package/dist/codegen/diff-manifest.js.map +1 -1
- package/dist/codegen/emit-includes.d.ts.map +1 -1
- package/dist/codegen/emit-includes.js +6 -6
- package/dist/codegen/emit-includes.js.map +1 -1
- package/dist/codegen/emit-models.d.ts.map +1 -1
- package/dist/codegen/emit-models.js +14 -14
- package/dist/codegen/emit-models.js.map +1 -1
- package/dist/codegen/generate.d.ts +11 -1
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +30 -11
- package/dist/codegen/generate.js.map +1 -1
- package/dist/codegen/manifest-relations.d.ts +2 -1
- package/dist/codegen/manifest-relations.d.ts.map +1 -1
- package/dist/codegen/manifest-relations.js +4 -27
- package/dist/codegen/manifest-relations.js.map +1 -1
- package/dist/codegen/schema-to-manifest.d.ts +3 -2
- package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
- package/dist/codegen/schema-to-manifest.js +9 -38
- package/dist/codegen/schema-to-manifest.js.map +1 -1
- package/dist/config.d.ts +6 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -13
- package/dist/config.js.map +1 -1
- package/dist/datasource-provider.d.ts +7 -0
- package/dist/datasource-provider.d.ts.map +1 -0
- package/dist/datasource-provider.js +11 -0
- package/dist/datasource-provider.js.map +1 -0
- package/dist/dialect/fk.d.ts.map +1 -1
- package/dist/dialect/fk.js +4 -2
- package/dist/dialect/fk.js.map +1 -1
- package/dist/dialect/postgres.d.ts.map +1 -1
- package/dist/dialect/postgres.js +11 -15
- package/dist/dialect/postgres.js.map +1 -1
- package/dist/dialect/shared.d.ts +3 -1
- package/dist/dialect/shared.d.ts.map +1 -1
- package/dist/dialect/shared.js +4 -0
- package/dist/dialect/shared.js.map +1 -1
- package/dist/dialect/sqlite.d.ts.map +1 -1
- package/dist/dialect/sqlite.js +15 -18
- package/dist/dialect/sqlite.js.map +1 -1
- package/dist/dialect/sqlite.test.d.ts +2 -0
- package/dist/dialect/sqlite.test.d.ts.map +1 -0
- package/dist/dialect/sqlite.test.js +122 -0
- package/dist/dialect/sqlite.test.js.map +1 -0
- package/dist/dialect/types.d.ts +2 -1
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/docs/render.d.ts.map +1 -1
- package/dist/docs/render.js.map +1 -1
- package/dist/docs/search.d.ts.map +1 -1
- package/dist/docs/search.js +4 -4
- package/dist/docs/search.js.map +1 -1
- package/dist/docs/server.d.ts.map +1 -1
- package/dist/docs/server.js +3 -3
- package/dist/docs/server.js.map +1 -1
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/init/scaffold.d.ts.map +1 -1
- package/dist/init/scaffold.js +3 -1
- package/dist/init/scaffold.js.map +1 -1
- package/dist/introspect/pull.d.ts.map +1 -1
- package/dist/introspect/pull.js +209 -154
- package/dist/introspect/pull.js.map +1 -1
- package/dist/introspect/queries.d.ts +6 -0
- package/dist/introspect/queries.d.ts.map +1 -1
- package/dist/introspect/queries.js +19 -0
- package/dist/introspect/queries.js.map +1 -1
- package/dist/introspect/sqlite/to-manifest.d.ts.map +1 -1
- package/dist/introspect/sqlite/to-manifest.js +6 -9
- package/dist/introspect/sqlite/to-manifest.js.map +1 -1
- package/dist/introspect/to-manifest.d.ts +8 -1
- package/dist/introspect/to-manifest.d.ts.map +1 -1
- package/dist/introspect/to-manifest.js +96 -21
- package/dist/introspect/to-manifest.js.map +1 -1
- package/dist/migrate/runner.d.ts +16 -0
- package/dist/migrate/runner.d.ts.map +1 -1
- package/dist/migrate/runner.js +147 -24
- package/dist/migrate/runner.js.map +1 -1
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +15 -16
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/plugins/json/operators.d.ts.map +1 -1
- package/dist/plugins/json/operators.js +100 -27
- package/dist/plugins/json/operators.js.map +1 -1
- package/dist/plugins/postgis/columns.d.ts +18 -4
- package/dist/plugins/postgis/columns.d.ts.map +1 -1
- package/dist/plugins/postgis/columns.js +69 -10
- package/dist/plugins/postgis/columns.js.map +1 -1
- package/dist/plugins/postgis/columns.test.d.ts +2 -0
- package/dist/plugins/postgis/columns.test.d.ts.map +1 -0
- package/dist/plugins/postgis/columns.test.js +67 -0
- package/dist/plugins/postgis/columns.test.js.map +1 -0
- package/dist/plugins/postgis/index.d.ts +4 -2
- package/dist/plugins/postgis/index.d.ts.map +1 -1
- package/dist/plugins/postgis/index.js +2 -1
- package/dist/plugins/postgis/index.js.map +1 -1
- package/dist/plugins/postgis/where-types.d.ts +18 -0
- package/dist/plugins/postgis/where-types.d.ts.map +1 -0
- package/dist/plugins/postgis/where-types.js +2 -0
- package/dist/plugins/postgis/where-types.js.map +1 -0
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +20 -5
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/types.d.ts +1 -1
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/runtime/client.d.ts +60 -11
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +85 -39
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/client.test.d.ts +2 -0
- package/dist/runtime/client.test.d.ts.map +1 -0
- package/dist/runtime/client.test.js +170 -0
- package/dist/runtime/client.test.js.map +1 -0
- package/dist/runtime/compile-error.d.ts +14 -0
- package/dist/runtime/compile-error.d.ts.map +1 -0
- package/dist/runtime/compile-error.js +14 -0
- package/dist/runtime/compile-error.js.map +1 -0
- package/dist/runtime/driver-sqlite.test.d.ts +2 -0
- package/dist/runtime/driver-sqlite.test.d.ts.map +1 -0
- package/dist/runtime/driver-sqlite.test.js +195 -0
- package/dist/runtime/driver-sqlite.test.js.map +1 -0
- package/dist/runtime/driver.d.ts +16 -1
- package/dist/runtime/driver.d.ts.map +1 -1
- package/dist/runtime/driver.js +118 -96
- package/dist/runtime/driver.js.map +1 -1
- package/dist/runtime/error-builders.d.ts +7 -6
- package/dist/runtime/error-builders.d.ts.map +1 -1
- package/dist/runtime/error-builders.js +2 -2
- package/dist/runtime/error-builders.js.map +1 -1
- package/dist/runtime/error-codes.d.ts +50 -0
- package/dist/runtime/error-codes.d.ts.map +1 -0
- package/dist/runtime/error-codes.js +54 -0
- package/dist/runtime/error-codes.js.map +1 -0
- package/dist/runtime/error-hints.d.ts +1 -0
- package/dist/runtime/error-hints.d.ts.map +1 -1
- package/dist/runtime/error-hints.js +13 -1
- package/dist/runtime/error-hints.js.map +1 -1
- package/dist/runtime/errors.d.ts +55 -8
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +143 -12
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/errors.test.d.ts +2 -0
- package/dist/runtime/errors.test.d.ts.map +1 -0
- package/dist/runtime/errors.test.js +115 -0
- package/dist/runtime/errors.test.js.map +1 -0
- package/dist/runtime/executor.d.ts +22 -3
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +93 -39
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/parse-db-json.d.ts +8 -0
- package/dist/runtime/parse-db-json.d.ts.map +1 -0
- package/dist/runtime/parse-db-json.js +33 -0
- package/dist/runtime/parse-db-json.js.map +1 -0
- package/dist/runtime/pg-error.d.ts.map +1 -1
- package/dist/runtime/pg-error.js +16 -16
- package/dist/runtime/pg-error.js.map +1 -1
- package/dist/runtime/pg-rollback.test.d.ts +2 -0
- package/dist/runtime/pg-rollback.test.d.ts.map +1 -0
- package/dist/runtime/pg-rollback.test.js +39 -0
- package/dist/runtime/pg-rollback.test.js.map +1 -0
- package/dist/runtime/query/aggregate.d.ts.map +1 -1
- package/dist/runtime/query/aggregate.js +15 -4
- package/dist/runtime/query/aggregate.js.map +1 -1
- package/dist/runtime/query/compile-aggregate.d.ts +39 -0
- package/dist/runtime/query/compile-aggregate.d.ts.map +1 -0
- package/dist/runtime/query/compile-aggregate.js +456 -0
- package/dist/runtime/query/compile-aggregate.js.map +1 -0
- package/dist/runtime/query/compile-where.d.ts +32 -0
- package/dist/runtime/query/compile-where.d.ts.map +1 -0
- package/dist/runtime/query/compile-where.js +708 -0
- package/dist/runtime/query/compile-where.js.map +1 -0
- package/dist/runtime/query/compile-write.d.ts +53 -0
- package/dist/runtime/query/compile-write.d.ts.map +1 -0
- package/dist/runtime/query/compile-write.js +467 -0
- package/dist/runtime/query/compile-write.js.map +1 -0
- package/dist/runtime/query/compile.d.ts +3 -114
- package/dist/runtime/query/compile.d.ts.map +1 -1
- package/dist/runtime/query/compile.js +3 -1536
- package/dist/runtime/query/compile.js.map +1 -1
- package/dist/runtime/query/count.d.ts +1 -0
- package/dist/runtime/query/count.d.ts.map +1 -1
- package/dist/runtime/query/count.js +12 -14
- package/dist/runtime/query/count.js.map +1 -1
- package/dist/runtime/query/create.d.ts.map +1 -1
- package/dist/runtime/query/create.js +20 -49
- package/dist/runtime/query/create.js.map +1 -1
- package/dist/runtime/query/cursor-codec.d.ts.map +1 -1
- package/dist/runtime/query/cursor-codec.js +8 -2
- package/dist/runtime/query/cursor-codec.js.map +1 -1
- package/dist/runtime/query/cursor.d.ts.map +1 -1
- package/dist/runtime/query/cursor.js +31 -7
- package/dist/runtime/query/cursor.js.map +1 -1
- package/dist/runtime/query/delete.d.ts.map +1 -1
- package/dist/runtime/query/delete.js +17 -17
- package/dist/runtime/query/delete.js.map +1 -1
- package/dist/runtime/query/execute.d.ts.map +1 -1
- package/dist/runtime/query/execute.js +11 -12
- package/dist/runtime/query/execute.js.map +1 -1
- package/dist/runtime/query/find-or-create.d.ts +4 -2
- package/dist/runtime/query/find-or-create.d.ts.map +1 -1
- package/dist/runtime/query/find-or-create.js +53 -43
- package/dist/runtime/query/find-or-create.js.map +1 -1
- package/dist/runtime/query/find-or-create.test.d.ts +2 -0
- package/dist/runtime/query/find-or-create.test.d.ts.map +1 -0
- package/dist/runtime/query/find-or-create.test.js +189 -0
- package/dist/runtime/query/find-or-create.test.js.map +1 -0
- package/dist/runtime/query/find.d.ts.map +1 -1
- package/dist/runtime/query/find.js +86 -58
- package/dist/runtime/query/find.js.map +1 -1
- package/dist/runtime/query/group-by.d.ts.map +1 -1
- package/dist/runtime/query/group-by.js +3 -2
- package/dist/runtime/query/group-by.js.map +1 -1
- package/dist/runtime/query/manifest-lookup.d.ts.map +1 -1
- package/dist/runtime/query/manifest-lookup.js +1 -1
- package/dist/runtime/query/manifest-lookup.js.map +1 -1
- package/dist/runtime/query/map-row.d.ts.map +1 -1
- package/dist/runtime/query/map-row.js +1 -1
- package/dist/runtime/query/map-row.js.map +1 -1
- package/dist/runtime/query/paginate.d.ts +3 -0
- package/dist/runtime/query/paginate.d.ts.map +1 -1
- package/dist/runtime/query/paginate.js +49 -11
- package/dist/runtime/query/paginate.js.map +1 -1
- package/dist/runtime/query/primary-key.d.ts +1 -1
- package/dist/runtime/query/primary-key.d.ts.map +1 -1
- package/dist/runtime/query/primary-key.js +45 -8
- package/dist/runtime/query/primary-key.js.map +1 -1
- package/dist/runtime/query/projection.d.ts +1 -0
- package/dist/runtime/query/projection.d.ts.map +1 -1
- package/dist/runtime/query/projection.js +23 -7
- package/dist/runtime/query/projection.js.map +1 -1
- package/dist/runtime/query/relation-planner.d.ts.map +1 -1
- package/dist/runtime/query/relation-planner.js +82 -53
- package/dist/runtime/query/relation-planner.js.map +1 -1
- package/dist/runtime/query/relation-writes.d.ts +3 -1
- package/dist/runtime/query/relation-writes.d.ts.map +1 -1
- package/dist/runtime/query/relation-writes.js +145 -54
- package/dist/runtime/query/relation-writes.js.map +1 -1
- package/dist/runtime/query/serialize-dialect.test.d.ts +2 -0
- package/dist/runtime/query/serialize-dialect.test.d.ts.map +1 -0
- package/dist/runtime/query/serialize-dialect.test.js +79 -0
- package/dist/runtime/query/serialize-dialect.test.js.map +1 -0
- package/dist/runtime/query/sqlite-constraint-errors.test.d.ts +2 -0
- package/dist/runtime/query/sqlite-constraint-errors.test.d.ts.map +1 -0
- package/dist/runtime/query/sqlite-constraint-errors.test.js +60 -0
- package/dist/runtime/query/sqlite-constraint-errors.test.js.map +1 -0
- package/dist/runtime/query/strip.d.ts.map +1 -1
- package/dist/runtime/query/strip.js.map +1 -1
- package/dist/runtime/query/table-index.d.ts.map +1 -1
- package/dist/runtime/query/table-index.js +4 -3
- package/dist/runtime/query/table-index.js.map +1 -1
- package/dist/runtime/query/unique.d.ts +8 -1
- package/dist/runtime/query/unique.d.ts.map +1 -1
- package/dist/runtime/query/unique.js +89 -4
- package/dist/runtime/query/unique.js.map +1 -1
- package/dist/runtime/query/unique.test.d.ts +2 -0
- package/dist/runtime/query/unique.test.d.ts.map +1 -0
- package/dist/runtime/query/unique.test.js +231 -0
- package/dist/runtime/query/unique.test.js.map +1 -0
- package/dist/runtime/query/update.d.ts.map +1 -1
- package/dist/runtime/query/update.js +30 -35
- package/dist/runtime/query/update.js.map +1 -1
- package/dist/runtime/query/update.test.d.ts +2 -0
- package/dist/runtime/query/update.test.d.ts.map +1 -0
- package/dist/runtime/query/update.test.js +186 -0
- package/dist/runtime/query/update.test.js.map +1 -0
- package/dist/runtime/query/updated-at.d.ts.map +1 -1
- package/dist/runtime/query/updated-at.js.map +1 -1
- package/dist/runtime/query/upsert.d.ts.map +1 -1
- package/dist/runtime/query/upsert.js +5 -7
- package/dist/runtime/query/upsert.js.map +1 -1
- package/dist/runtime/schema-error.d.ts.map +1 -1
- package/dist/runtime/schema-error.js +6 -2
- package/dist/runtime/schema-error.js.map +1 -1
- package/dist/runtime/sqlite-error.d.ts.map +1 -1
- package/dist/runtime/sqlite-error.js +11 -12
- package/dist/runtime/sqlite-error.js.map +1 -1
- package/dist/runtime/sqlite-open.d.ts +2 -0
- package/dist/runtime/sqlite-open.d.ts.map +1 -1
- package/dist/runtime/sqlite-open.js +18 -1
- package/dist/runtime/sqlite-open.js.map +1 -1
- package/dist/runtime/transaction.d.ts +4 -0
- package/dist/runtime/transaction.d.ts.map +1 -1
- package/dist/runtime/transaction.js +31 -1
- package/dist/runtime/transaction.js.map +1 -1
- package/dist/runtime/transaction.test.d.ts +2 -0
- package/dist/runtime/transaction.test.d.ts.map +1 -0
- package/dist/runtime/transaction.test.js +43 -0
- package/dist/runtime/transaction.test.js.map +1 -0
- package/dist/runtime/types.d.ts +50 -17
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/schema/column-where.d.ts +11 -1
- package/dist/schema/column-where.d.ts.map +1 -1
- package/dist/schema/column.d.ts +1 -1
- package/dist/schema/column.d.ts.map +1 -1
- package/dist/schema/column.js +1 -1
- package/dist/schema/column.js.map +1 -1
- package/dist/schema/define-schema.d.ts +1 -1
- package/dist/schema/define-schema.d.ts.map +1 -1
- package/dist/schema/define-schema.js +5 -23
- package/dist/schema/define-schema.js.map +1 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/relation-types.d.ts +13 -17
- package/dist/schema/relation-types.d.ts.map +1 -1
- package/dist/schema/relation.d.ts.map +1 -1
- package/dist/schema/relation.js +6 -4
- package/dist/schema/relation.js.map +1 -1
- package/dist/schema/table-registry.d.ts.map +1 -1
- package/dist/schema/table-registry.js +8 -2
- package/dist/schema/table-registry.js.map +1 -1
- package/dist/schema/table.d.ts +6 -3
- package/dist/schema/table.d.ts.map +1 -1
- package/dist/schema/table.js +30 -31
- package/dist/schema/table.js.map +1 -1
- package/dist/schema/types.d.ts +33 -25
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/sql/builder.d.ts +5 -1
- package/dist/sql/builder.d.ts.map +1 -1
- package/dist/sql/builder.js +23 -3
- package/dist/sql/builder.js.map +1 -1
- package/dist/sql/index.d.ts +5 -2
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js +5 -2
- package/dist/sql/index.js.map +1 -1
- package/dist/utils/case.d.ts +4 -0
- package/dist/utils/case.d.ts.map +1 -1
- package/dist/utils/case.js +11 -0
- package/dist/utils/case.js.map +1 -1
- package/dist/utils/dotenv.d.ts +8 -0
- package/dist/utils/dotenv.d.ts.map +1 -0
- package/dist/utils/dotenv.js +70 -0
- package/dist/utils/dotenv.js.map +1 -0
- package/dist/utils/dotenv.test.d.ts +2 -0
- package/dist/utils/dotenv.test.d.ts.map +1 -0
- package/dist/utils/dotenv.test.js +32 -0
- package/dist/utils/dotenv.test.js.map +1 -0
- package/dist/utils/inflect.d.ts +6 -0
- package/dist/utils/inflect.d.ts.map +1 -0
- package/dist/utils/inflect.js +80 -0
- package/dist/utils/inflect.js.map +1 -0
- package/docs/cli.md +6 -2
- package/docs/configuration.md +3 -1
- package/docs/errors.md +61 -6
- package/docs/examples.md +63 -13
- package/docs/getting-started.md +24 -1
- package/docs/migrations.md +15 -2
- package/docs/plugins.md +13 -1
- package/docs/queries.md +109 -10
- package/docs/relations.md +5 -1
- package/docs/schema.md +12 -5
- package/docs/sqlite.md +24 -10
- package/docs/transactions.md +13 -2
- package/package.json +4 -2
package/docs/cli.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# CLI reference
|
|
2
2
|
|
|
3
|
+
Commands that read `neoorm.config.ts` load `.env` from the current working directory first. Existing environment variables take precedence.
|
|
4
|
+
|
|
3
5
|
## `neoorm init`
|
|
4
6
|
|
|
5
7
|
Scaffold a new NeoOrm project.
|
|
@@ -11,7 +13,7 @@ neoorm init [options]
|
|
|
11
13
|
Creates `neoorm.config.ts`, `schema.ts`, and `.env.example` only — no codegen or migrations. Run `neoorm migrate dev` afterwards to generate the client and first migration.
|
|
12
14
|
|
|
13
15
|
Options:
|
|
14
|
-
- `--provider <provider>` — `postgresql` (default) or `sqlite`. If omitted and running interactively, prompts to choose.
|
|
16
|
+
- `--provider <provider>` — `postgresql` (default) or `sqlite`. `postgres` and `pg` are aliases of `postgresql`. If omitted and running interactively, prompts to choose.
|
|
15
17
|
- `--database-url <url>` — override database URL / file path (default: `postgresql://postgres:postgres@localhost:5432/myapp` for postgres, `./dev.db` for sqlite)
|
|
16
18
|
- `--schema <path>` — schema file path (default: `./schema.ts`)
|
|
17
19
|
- `--out <dir>` — generated output directory (default: `./neoorm`)
|
|
@@ -77,6 +79,8 @@ neoorm migrate reset --force [--skip-apply]
|
|
|
77
79
|
- `--force` — required (safety guard)
|
|
78
80
|
- `--skip-apply` — only drop, don't re-apply
|
|
79
81
|
|
|
82
|
+
PostgreSQL: the connecting role owns the recreated schema. `PUBLIC` is not granted.
|
|
83
|
+
|
|
80
84
|
## `neoorm docs`
|
|
81
85
|
|
|
82
86
|
Serve the NeoOrm documentation locally in your browser.
|
|
@@ -94,7 +98,7 @@ The sidebar includes a search box with live results across all documentation pag
|
|
|
94
98
|
|
|
95
99
|
## `neoorm db push`
|
|
96
100
|
|
|
97
|
-
Push the current
|
|
101
|
+
Push the current `schema.ts` to the database without going through the migration ledger. You do not need to run `generate` first — push compiles the schema file you just saved. After a successful push, `snapshot.json` is updated so later `generate` does not emit the same DDL again.
|
|
98
102
|
|
|
99
103
|
```
|
|
100
104
|
neoorm db push
|
package/docs/configuration.md
CHANGED
|
@@ -18,13 +18,15 @@ export default defineConfig({
|
|
|
18
18
|
});
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
CLI commands load `.env` from the project directory before evaluating this file, so `process.env.DATABASE_URL` is set after `cp .env.example .env`. Variables already present in the environment are not overwritten. The generated client does not load `.env` at query time — use your runtime or a loader such as `node --env-file=.env`.
|
|
22
|
+
|
|
21
23
|
## Options
|
|
22
24
|
|
|
23
25
|
| Key | Type | Default | Description |
|
|
24
26
|
|-----|------|---------|-------------|
|
|
25
27
|
| `schema` | `string` | required | Path to the schema file |
|
|
26
28
|
| `out` | `string` | required | Output directory for generated files |
|
|
27
|
-
| `datasource.provider` | `"postgresql" \| "sqlite"` | required | Database provider |
|
|
29
|
+
| `datasource.provider` | `"postgresql" \| "postgres" \| "sqlite"` | required | Database provider. `"postgres"` is an alias of `"postgresql"`. |
|
|
28
30
|
| `datasource.url` | `string` | required | Connection string (PostgreSQL) or database file path / `:memory:` (SQLite) |
|
|
29
31
|
| `datasource.schema` | `string` | `"public"` | PostgreSQL schema for migrations and queries (SQLite: not applicable) |
|
|
30
32
|
| `datasource.enum` | `"check" \| "union" \| "native"` | `"check"` | How to store enum columns |
|
package/docs/errors.md
CHANGED
|
@@ -1,27 +1,82 @@
|
|
|
1
1
|
# Common errors
|
|
2
2
|
|
|
3
|
-
NeoOrm surfaces structured errors with
|
|
3
|
+
NeoOrm surfaces structured errors with dialect-agnostic codes, optional subclasses for constraint violations, and fix suggestions. Catch them programmatically or read the CLI output after `neoorm generate` / query failures.
|
|
4
4
|
|
|
5
5
|
## Error types
|
|
6
6
|
|
|
7
|
+
- **`NeoOrmError`** — base class for all NeoOrm errors
|
|
7
8
|
- **`NeoOrmSchemaError`** — schema compilation (`neoorm generate`) or migration failures
|
|
8
9
|
- **`NeoOrmQueryError`** — query builder mistakes (compile phase) or database runtime failures
|
|
10
|
+
- **Constraint subclasses** — `UniqueViolationError`, `ForeignKeyViolationError`, `NotNullViolationError`, `CheckViolationError`, `InvalidInputError`, `SchemaDriftError`
|
|
11
|
+
- **`QueryCompileError`** — compile-time query builder mistakes (`phase: "compile"`)
|
|
12
|
+
- **`NeoOrmDriverError`** — unclassified driver/SQL failures (`driver_error`)
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
Every error exposes **`err.code`** (typed string) and **`err.context`** with `detail`, `suggestions`, and optional table/column metadata.
|
|
11
15
|
|
|
12
16
|
```ts
|
|
13
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
UniqueViolationError,
|
|
19
|
+
ForeignKeyViolationError,
|
|
20
|
+
NeoOrmQueryError,
|
|
21
|
+
QueryErrorCode,
|
|
22
|
+
isUniqueViolation,
|
|
23
|
+
} from "neoorm";
|
|
14
24
|
|
|
15
25
|
try {
|
|
16
26
|
await db.users.create({ data: { email: "a@b.com" } });
|
|
17
27
|
} catch (err) {
|
|
28
|
+
if (err instanceof UniqueViolationError) {
|
|
29
|
+
return { status: 409, column: err.context.columnTsName };
|
|
30
|
+
}
|
|
31
|
+
if (err instanceof ForeignKeyViolationError) {
|
|
32
|
+
return { status: 400 };
|
|
33
|
+
}
|
|
18
34
|
if (err instanceof NeoOrmQueryError) {
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
switch (err.code) {
|
|
36
|
+
case QueryErrorCode.not_null_violation:
|
|
37
|
+
case QueryErrorCode.invalid_input:
|
|
38
|
+
return { status: 400 };
|
|
39
|
+
default:
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
21
42
|
}
|
|
43
|
+
throw err;
|
|
22
44
|
}
|
|
23
45
|
```
|
|
24
46
|
|
|
47
|
+
Type guards (`isUniqueViolation`, `isQueryCompileError`, `isNeoOrmError`, …) are exported for bundle-boundary safety.
|
|
48
|
+
|
|
49
|
+
## Query error codes
|
|
50
|
+
|
|
51
|
+
Codes are **dialect-agnostic** — the same `unique_violation` code is used for PostgreSQL and SQLite.
|
|
52
|
+
|
|
53
|
+
| Code | Subclass | Typical HTTP | When |
|
|
54
|
+
|------|----------|--------------|------|
|
|
55
|
+
| `unique_violation` | `UniqueViolationError` | 409 | Duplicate unique key |
|
|
56
|
+
| `foreign_key_violation` | `ForeignKeyViolationError` | 400 | Missing parent row |
|
|
57
|
+
| `not_null_violation` | `NotNullViolationError` | 400 | Required column null |
|
|
58
|
+
| `check_violation` | `CheckViolationError` | 400 | Check/enum rejected |
|
|
59
|
+
| `invalid_input` | `InvalidInputError` | 400 | Type mismatch (PG `22P02`) |
|
|
60
|
+
| `relation_not_found` | `SchemaDriftError` | 500 | Table missing (PG `42P01`) |
|
|
61
|
+
| `column_not_found` | `SchemaDriftError` | 500 | Column missing (PG `42703`) |
|
|
62
|
+
| `empty_returning` | — | 500 | INSERT … RETURNING returned no row |
|
|
63
|
+
| `connection_error` | — | 503 | `$connect` failed |
|
|
64
|
+
| `driver_error` | `NeoOrmDriverError` | 500 | Unclassified driver/SQL failure |
|
|
65
|
+
| `unknown_table` | `QueryCompileError` | 400 | Bad table accessor |
|
|
66
|
+
| `unknown_column` | `QueryCompileError` | 400 | Bad column in where/select/omit/groupBy |
|
|
67
|
+
| `unknown_relation` | `QueryCompileError` | 400 | Bad relation name on `with` or nested writes |
|
|
68
|
+
| `invalid_args` | `QueryCompileError` | 400 | Unsupported where/having operator |
|
|
69
|
+
| `invalid_nested_write` | `QueryCompileError` | 400 | Relation field is not a pure nested write bag |
|
|
70
|
+
| `invalid_cursor` | `QueryCompileError` | 400 | Malformed or mismatched pagination cursor |
|
|
71
|
+
| `unique_where_invalid` | `QueryCompileError` | 400 | unique where is not unique, or uses filter operators |
|
|
72
|
+
| `where_required` | `QueryCompileError` | 400 | update/delete missing where |
|
|
73
|
+
| `missing_primary_key` | `QueryCompileError` | 400 | Table or RETURNING row is missing a primary key |
|
|
74
|
+
| `unsupported_operation` | `QueryCompileError` | 400 | Operator not supported on this dialect (e.g. SQLite `search`) |
|
|
75
|
+
|
|
76
|
+
PostgreSQL SQLSTATE is preserved on `err.context.pgCode` when available.
|
|
77
|
+
|
|
78
|
+
`findUnique` / `findFirst` return `null` when no row is found — they do not throw a not-found error.
|
|
79
|
+
|
|
25
80
|
## Schema: use accessors, not SQL names
|
|
26
81
|
|
|
27
82
|
Foreign keys and `many()` targets refer to **schema accessors** (the keys in `defineSchema({ ... })`), not SQL table names.
|
|
@@ -38,7 +93,7 @@ If you see `Foreign key references unknown table accessor "server_members"`, che
|
|
|
38
93
|
|
|
39
94
|
## Queries: use TypeScript column names
|
|
40
95
|
|
|
41
|
-
Filters, `select`, `omit`, `orderBy`, and `
|
|
96
|
+
Filters, `select`, `omit`, `orderBy`, `groupBy`, and `create`/`update` `data` use **TypeScript property names** from your schema, not SQL `snake_case` names. Unknown keys fail at compile time (`unknown_column`).
|
|
42
97
|
|
|
43
98
|
```ts
|
|
44
99
|
// Wrong
|
package/docs/examples.md
CHANGED
|
@@ -11,6 +11,7 @@ A small CMS-style schema used throughout these examples:
|
|
|
11
11
|
```ts
|
|
12
12
|
import {
|
|
13
13
|
bool,
|
|
14
|
+
decimal,
|
|
14
15
|
defineSchema,
|
|
15
16
|
enumType,
|
|
16
17
|
fk,
|
|
@@ -37,6 +38,7 @@ export const schema = defineSchema({
|
|
|
37
38
|
id: id(),
|
|
38
39
|
userId: fk("users").notNull().unique().onDelete("cascade"),
|
|
39
40
|
bio: text(),
|
|
41
|
+
avatarUrl: text(),
|
|
40
42
|
}),
|
|
41
43
|
|
|
42
44
|
posts: table({
|
|
@@ -48,6 +50,7 @@ export const schema = defineSchema({
|
|
|
48
50
|
views: int().notNull().default(0),
|
|
49
51
|
status: enumType(["draft", "published", "archived"]).notNull().default("draft"),
|
|
50
52
|
metadata: jsonb<Record<string, unknown>>(),
|
|
53
|
+
price: decimal({ precision: 10, scale: 2 }),
|
|
51
54
|
...timestamps(),
|
|
52
55
|
tags: many("tags"),
|
|
53
56
|
}),
|
|
@@ -79,7 +82,7 @@ import { db } from "./neoorm/client.js";
|
|
|
79
82
|
```ts
|
|
80
83
|
// Create
|
|
81
84
|
const user = await db.users.create({
|
|
82
|
-
data: { email: "alice@example.com", name: "Alice" },
|
|
85
|
+
data: { email: "alice@example.com", name: "Alice", password: "secret" },
|
|
83
86
|
});
|
|
84
87
|
|
|
85
88
|
// Create and return full row
|
|
@@ -151,7 +154,7 @@ const users = await db.users.findMany({
|
|
|
151
154
|
});
|
|
152
155
|
```
|
|
153
156
|
|
|
154
|
-
Omit columns at query time (
|
|
157
|
+
Omit columns at query time (dropped from the `SELECT`):
|
|
155
158
|
|
|
156
159
|
```ts
|
|
157
160
|
const user = await db.users.findById("user_1", {
|
|
@@ -276,6 +279,7 @@ const page = await db.posts.paginate({
|
|
|
276
279
|
where: { published: true },
|
|
277
280
|
orderBy: { createdAt: "desc" },
|
|
278
281
|
take: 20,
|
|
282
|
+
omit: { body: true },
|
|
279
283
|
...(cursor ? { after: cursor } : {}),
|
|
280
284
|
});
|
|
281
285
|
|
|
@@ -304,17 +308,25 @@ const stats = await db.posts.aggregate({
|
|
|
304
308
|
Batch steps (single transaction):
|
|
305
309
|
|
|
306
310
|
```ts
|
|
311
|
+
let authorId!: string;
|
|
307
312
|
const [author, post] = await db.$transaction([
|
|
308
|
-
(tx) =>
|
|
309
|
-
tx.users.create({
|
|
310
|
-
data: {
|
|
311
|
-
|
|
313
|
+
async (tx) => {
|
|
314
|
+
const created = await tx.users.create({
|
|
315
|
+
data: {
|
|
316
|
+
email: "author@example.com",
|
|
317
|
+
name: "Author",
|
|
318
|
+
password: "secret",
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
authorId = created.id;
|
|
322
|
+
return created;
|
|
323
|
+
},
|
|
312
324
|
(tx) =>
|
|
313
325
|
tx.posts.create({
|
|
314
326
|
data: {
|
|
315
327
|
title: "Transactional post",
|
|
316
328
|
body: "...",
|
|
317
|
-
author: { connect: { id:
|
|
329
|
+
author: { connect: { id: authorId } },
|
|
318
330
|
},
|
|
319
331
|
}),
|
|
320
332
|
]);
|
|
@@ -325,7 +337,7 @@ Interactive callback with rollback:
|
|
|
325
337
|
```ts
|
|
326
338
|
await db.$transaction(async (tx) => {
|
|
327
339
|
const user = await tx.users.create({
|
|
328
|
-
data: { email: "x@y.z", name: "Temp" },
|
|
340
|
+
data: { email: "x@y.z", name: "Temp", password: "secret" },
|
|
329
341
|
});
|
|
330
342
|
await tx.posts.create({
|
|
331
343
|
data: {
|
|
@@ -342,10 +354,14 @@ Nested savepoints:
|
|
|
342
354
|
|
|
343
355
|
```ts
|
|
344
356
|
await db.$transaction(async (tx) => {
|
|
345
|
-
await tx.users.create({
|
|
357
|
+
await tx.users.create({
|
|
358
|
+
data: { email: "outer@x.y", name: "Outer", password: "secret" },
|
|
359
|
+
});
|
|
346
360
|
|
|
347
361
|
await tx.$transaction(async (nested) => {
|
|
348
|
-
await nested.users.create({
|
|
362
|
+
await nested.users.create({
|
|
363
|
+
data: { email: "inner@x.y", name: "Inner", password: "secret" },
|
|
364
|
+
});
|
|
349
365
|
throw new Error("roll back inner only");
|
|
350
366
|
}).catch(() => undefined);
|
|
351
367
|
});
|
|
@@ -353,9 +369,11 @@ await db.$transaction(async (tx) => {
|
|
|
353
369
|
|
|
354
370
|
## Raw SQL
|
|
355
371
|
|
|
356
|
-
Use `db.sql` when you need full SQL control.
|
|
372
|
+
Use `db.sql` when you need full SQL control. It uses the same compiler as `neoorm/sql` (`sql`, `sqlId`, nested fragments). `sqlBuilder` is select/join/group/order only — interpolate `.compile()` into `db.sql` for WHERE, LIMIT, or bound params.
|
|
357
373
|
|
|
358
374
|
```ts
|
|
375
|
+
import { sql, sqlBuilder, sqlId } from "neoorm/sql";
|
|
376
|
+
|
|
359
377
|
const rows = await db.sql`
|
|
360
378
|
SELECT u.id, u.email, count(p.id) AS post_count
|
|
361
379
|
FROM users u
|
|
@@ -363,6 +381,19 @@ const rows = await db.sql`
|
|
|
363
381
|
GROUP BY u.id, u.email
|
|
364
382
|
ORDER BY post_count DESC
|
|
365
383
|
`;
|
|
384
|
+
|
|
385
|
+
const email = "a@b.com";
|
|
386
|
+
const ident = sqlId("users");
|
|
387
|
+
const filter = sql`email = ${email}`;
|
|
388
|
+
const named = await db.sql`SELECT * FROM ${ident} WHERE ${filter}`;
|
|
389
|
+
|
|
390
|
+
const grouped = sqlBuilder
|
|
391
|
+
.selectFrom("users")
|
|
392
|
+
.leftJoin("posts", "posts.author_id", "users.id")
|
|
393
|
+
.select(["users.id", "users.email"])
|
|
394
|
+
.groupBy("users.id", "users.email")
|
|
395
|
+
.compile();
|
|
396
|
+
const dashboard = await db.sql`${grouped} HAVING count(posts.id) > ${0}`;
|
|
366
397
|
```
|
|
367
398
|
|
|
368
399
|
## API responses (`strip`)
|
|
@@ -430,6 +461,16 @@ const db = createNeoOrmClient(manifest, {
|
|
|
430
461
|
|
|
431
462
|
See [SQLite](sqlite.md) for type mapping and limitations.
|
|
432
463
|
|
|
464
|
+
Log compiled SQL:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const db = createNeoOrmClient(manifest, {
|
|
468
|
+
afterQuery: ({ sql, durationMs }) => {
|
|
469
|
+
if (durationMs > 50) console.warn(`slow ${durationMs.toFixed(1)}ms`, sql);
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
433
474
|
## PostGIS
|
|
434
475
|
|
|
435
476
|
```ts
|
|
@@ -460,10 +501,10 @@ See [Plugins](plugins.md).
|
|
|
460
501
|
|
|
461
502
|
## Schema extras
|
|
462
503
|
|
|
463
|
-
Composite unique constraint and partial
|
|
504
|
+
Composite unique constraint and partial unique (soft-delete emails):
|
|
464
505
|
|
|
465
506
|
```ts
|
|
466
|
-
import { fk, id, index, table, text, unique } from "neoorm/schema";
|
|
507
|
+
import { bool, fk, id, index, table, text, unique } from "neoorm/schema";
|
|
467
508
|
|
|
468
509
|
posts: table(
|
|
469
510
|
{
|
|
@@ -477,6 +518,15 @@ posts: table(
|
|
|
477
518
|
index(t.title).where({ published: true }),
|
|
478
519
|
],
|
|
479
520
|
),
|
|
521
|
+
|
|
522
|
+
users: table(
|
|
523
|
+
{
|
|
524
|
+
id: id(),
|
|
525
|
+
email: text().notNull(),
|
|
526
|
+
deleted: bool().notNull().default(false),
|
|
527
|
+
},
|
|
528
|
+
(t) => [unique(t.email).where({ deleted: false })],
|
|
529
|
+
),
|
|
480
530
|
```
|
|
481
531
|
|
|
482
532
|
SQL name override when accessor differs from table name:
|
package/docs/getting-started.md
CHANGED
|
@@ -14,6 +14,8 @@ cp .env.example .env
|
|
|
14
14
|
bunx neoorm migrate dev
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
The CLI loads `.env` automatically. You do not need to export `DATABASE_URL` in the shell first.
|
|
18
|
+
|
|
17
19
|
Query:
|
|
18
20
|
|
|
19
21
|
```ts
|
|
@@ -119,4 +121,25 @@ const tenantDb = createNeoOrmClient(manifest, {
|
|
|
119
121
|
});
|
|
120
122
|
```
|
|
121
123
|
|
|
122
|
-
Raw `db.sql` / `db.execute` are not rewritten
|
|
124
|
+
Raw `db.sql` uses the same compiler as `neoorm/sql`. `db.sql` / `db.execute` are not rewritten for tenant `schema` — qualify identifiers yourself.
|
|
125
|
+
|
|
126
|
+
Reuse an existing `pg` pool with `createNeoOrmClientFromPool(manifest, pool)`, or an existing SQLite handle with `createNeoOrmClientFromSqlite(manifest, database)`. `$disconnect()` does not close a borrowed pool or database — close it yourself when every consumer is done.
|
|
127
|
+
|
|
128
|
+
Log compiled SQL with `beforeQuery` / `afterQuery` on the same options object (see [Queries](queries.md#logging-sql)).
|
|
129
|
+
|
|
130
|
+
PostgreSQL pool settings (`ssl`, timeouts, `application_name`, size) go on `pool` and are passed through to `pg.Pool`:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const db = createNeoOrmClient(manifest, {
|
|
134
|
+
connectionString: process.env.DATABASE_URL!,
|
|
135
|
+
pool: {
|
|
136
|
+
max: 10,
|
|
137
|
+
connectionTimeoutMillis: 5_000,
|
|
138
|
+
statement_timeout: 15_000,
|
|
139
|
+
application_name: "api",
|
|
140
|
+
ssl: { rejectUnauthorized: true },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For full `pg.Pool` control (custom host/user, event handlers), construct the pool yourself and pass it to `createNeoOrmClientFromPool`.
|
package/docs/migrations.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| `neoorm migrate status` | List applied vs pending migrations |
|
|
12
12
|
| `neoorm migrate down [--steps N]` | Roll back the last N applied migrations (default 1) |
|
|
13
13
|
| `neoorm migrate reset --force` | Drop the `public` schema (PostgreSQL) or all tables (SQLite) and re-apply migrations (local dev) |
|
|
14
|
-
| `neoorm db push` | Push the current
|
|
14
|
+
| `neoorm db push` | Push the current `schema.ts` to the database (no migration file) |
|
|
15
15
|
| `neoorm db pull` | Introspect the database into a schema file |
|
|
16
16
|
|
|
17
17
|
## Generate outcomes
|
|
@@ -31,6 +31,17 @@ When migration is blocked, the CLI explains why — for example unsupported type
|
|
|
31
31
|
neoorm generate --accept-data-loss
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Deploy locking and checksums
|
|
35
|
+
|
|
36
|
+
`neoorm migrate deploy` serializes concurrent runs and records a SHA-256 checksum of each applied `migration.sql`:
|
|
37
|
+
|
|
38
|
+
| Dialect | Lock |
|
|
39
|
+
|---------|------|
|
|
40
|
+
| PostgreSQL | `pg_advisory_xact_lock` on the deploy transaction |
|
|
41
|
+
| SQLite | `BEGIN IMMEDIATE` around the whole deploy |
|
|
42
|
+
|
|
43
|
+
The ledger table `_neoorm_migrations` stores `name` and `checksum`. Editing an already-applied `migration.sql` is rejected (`migration_guard`) — restore the original file or add a new migration. Pending migrations in one deploy run apply in that locked transaction; if a later statement fails, none of that run's new ledger rows remain.
|
|
44
|
+
|
|
34
45
|
## Status
|
|
35
46
|
|
|
36
47
|
```bash
|
|
@@ -47,6 +58,8 @@ neoorm migrate reset --force
|
|
|
47
58
|
|
|
48
59
|
Drops the `public` schema (PostgreSQL) or all tables (SQLite) and re-applies all migrations from disk. Requires `--force`. Use `--skip-apply` to only drop without re-applying.
|
|
49
60
|
|
|
61
|
+
PostgreSQL reset recreates the schema owned by the connecting role. It does not `GRANT ALL ON SCHEMA … TO PUBLIC`.
|
|
62
|
+
|
|
50
63
|
## Rollback
|
|
51
64
|
|
|
52
65
|
`down.sql` and `snapshot.before.json` are written automatically when `neoorm generate` or `neoorm migrate dev` creates a migration. The down SQL is the reverse schema diff (`next → prev`), with destructive changes accepted so rollbacks can drop columns or tables added in the forward migration.
|
|
@@ -62,6 +75,6 @@ Legacy migrations without `down.sql` cannot be rolled back — re-generate the m
|
|
|
62
75
|
|
|
63
76
|
Migration folders are not deleted on rollback (same as Prisma). Re-run `neoorm migrate deploy` to re-apply rolled-back migrations.
|
|
64
77
|
|
|
65
|
-
After rollback, `schema.ts` may still describe a newer schema than the restored snapshot; `neoorm migrate dev` may generate a new forward migration. `db push` and `migrate down` are independent — push ignores the migration ledger.
|
|
78
|
+
After rollback, `schema.ts` may still describe a newer schema than the restored snapshot; `neoorm migrate dev` may generate a new forward migration. `db push` and `migrate down` are independent — push ignores the migration ledger. `db push` compiles `schema.ts` (not `snapshot.json`) and updates `snapshot.json` after a successful push so a later `generate` does not emit DDL already applied to the database.
|
|
66
79
|
|
|
67
80
|
For a full wipe during local development, use `neoorm migrate reset --force` instead.
|
package/docs/plugins.md
CHANGED
|
@@ -30,7 +30,9 @@ places: table({
|
|
|
30
30
|
}),
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
`srid` requires a whitelisted `subtype`. PostGIS typmod is `geometry(Point,4326)`, not `geometry(4326)`. Use `subtype: "Geometry"` to fix SRID without constraining the type. Allowed subtypes include `Point`, `LineString`, `Polygon`, `MultiPolygon`, `GeometryCollection`, and `Geometry`, optionally with `Z`, `M`, or `ZM` (e.g. `PointZ`).
|
|
34
|
+
|
|
35
|
+
Spatial `where` operators: `intersects`, `within`, `dWithin`. Importing `neoorm/plugins/postgis` merges those keys into `WhereInput` for spatial columns.
|
|
34
36
|
|
|
35
37
|
## Citext
|
|
36
38
|
|
|
@@ -45,3 +47,13 @@ users: table({
|
|
|
45
47
|
## Custom plugins
|
|
46
48
|
|
|
47
49
|
See the plugin registry source for `ColumnTypePlugin` and `NeoOrmPlugin` interfaces.
|
|
50
|
+
|
|
51
|
+
To type `where` operators, augment `PluginColumnWhereOperators` on `neoorm/schema` with your column kind:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
declare module "neoorm/schema" {
|
|
55
|
+
interface PluginColumnWhereOperators {
|
|
56
|
+
myKind: { overlaps?: string };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|