@antelopejs/interface-database-decorators 0.2.0 → 0.2.2

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 (45) hide show
  1. package/dist/common.js +7 -5
  2. package/dist/common.js.map +1 -1
  3. package/dist/model.js +21 -11
  4. package/dist/model.js.map +1 -1
  5. package/dist/modifiers/common.d.ts +4 -29
  6. package/dist/modifiers/common.js +12 -66
  7. package/dist/modifiers/common.js.map +1 -1
  8. package/dist/modifiers/encryption.js +32 -1
  9. package/dist/modifiers/encryption.js.map +1 -1
  10. package/dist/modifiers/hash.d.ts +3 -1
  11. package/dist/modifiers/hash.js +3 -1
  12. package/dist/modifiers/hash.js.map +1 -1
  13. package/dist/modifiers/internal/classes.d.ts +45 -0
  14. package/dist/modifiers/internal/classes.js +131 -0
  15. package/dist/modifiers/internal/classes.js.map +1 -0
  16. package/dist/modifiers/internal/records.d.ts +6 -0
  17. package/dist/modifiers/internal/records.js +12 -1
  18. package/dist/modifiers/internal/records.js.map +1 -1
  19. package/dist/modifiers/localization.d.ts +30 -4
  20. package/dist/modifiers/localization.js +8 -20
  21. package/dist/modifiers/localization.js.map +1 -1
  22. package/dist/tests/hash_upgrade.test.js +6 -2
  23. package/dist/tests/hash_upgrade.test.js.map +1 -1
  24. package/dist/tests/localized_query_keys.test.d.ts +1 -0
  25. package/dist/tests/localized_query_keys.test.js +63 -0
  26. package/dist/tests/localized_query_keys.test.js.map +1 -0
  27. package/dist/tests/metadata_inheritance.test.js +63 -0
  28. package/dist/tests/metadata_inheritance.test.js.map +1 -1
  29. package/dist/tests/modifiers/common.test.js +6 -0
  30. package/dist/tests/modifiers/common.test.js.map +1 -1
  31. package/dist/tests/modifiers/encryption_date_marker.test.js +22 -0
  32. package/dist/tests/modifiers/encryption_date_marker.test.js.map +1 -1
  33. package/dist/tests/modifiers/hash.test.js +10 -4
  34. package/dist/tests/modifiers/hash.test.js.map +1 -1
  35. package/dist/tests/modifiers/localization.test.js +134 -0
  36. package/dist/tests/modifiers/localization.test.js.map +1 -1
  37. package/dist/tests/modifiers/symbol_fields.test.d.ts +1 -0
  38. package/dist/tests/modifiers/symbol_fields.test.js +62 -0
  39. package/dist/tests/modifiers/symbol_fields.test.js.map +1 -0
  40. package/dist/tests/partial_update.test.js +79 -6
  41. package/dist/tests/partial_update.test.js.map +1 -1
  42. package/docs/3.table-modifiers.md +9 -6
  43. package/docs/4.data-models.md +2 -0
  44. package/package.json +4 -4
  45. package/skills/database-decorators-interface/SKILL.md +4 -1
@@ -110,13 +110,13 @@ A field with an `algorithm` stores the base64 digest of the row's salt followed
110
110
  The `HashModifier` mixin adds these methods to table instances:
111
111
 
112
112
  ```typescript fragment
113
- testHash<This extends {}, F extends keyof This>(this: This, field: F, value: This[F]): unknown
113
+ testHash<This extends {}, F extends keyof This>(this: This, field: F, value: This[F]): boolean
114
114
  testHashAsync<This extends {}, F extends keyof This>(this: This, field: F, value: This[F]): Promise<boolean>
115
115
  isHashOutdated<This extends {}>(this: This, field: keyof This): boolean
116
116
  ```
117
117
 
118
118
  - `testHashAsync` resolves whether `value` matches the stored hash. It runs scrypt on the thread pool, so use it on a server. It rejects with `Field <name> is not hashed` for a field without `@Hashed`.
119
- - `testHash` gives the same result and blocks the event loop while scrypt runs. Its declared return type is `unknown`. On a hashed field, the value is a boolean.
119
+ - `testHash` gives the same result and blocks the event loop while scrypt runs. It throws `Field <name> is not hashed` for a field without `@Hashed`.
120
120
  - `isHashOutdated` returns whether the stored hash differs from what the field writes today, which is any stored string that does not start with `$scrypt$ln=15,r=8,p=3$`. It returns `false` when nothing is stored, and always on a field with an `algorithm`. It throws `Field <name> is not hashed` for a field without `@Hashed`.
121
121
 
122
122
  #### Upgrade stored hashes
@@ -187,11 +187,11 @@ console.log(german.name); // "Premium Headphones" (fallback)
187
187
 
188
188
  #### Wildcard key
189
189
 
190
- The wildcard key `"*"` reads or writes all translations at once. `localize()` does not change the property's TypeScript type, so a field declared as `string` holds a record of texts by locale under `"*"`. Read that record as `unknown` and check it before use. Write it with `Object.assign`, which calls the field's setter:
190
+ The wildcard key `"*"` reads or writes all translations at once. List the localized fields with it, and `localize("*", fields)` types each listed field as its values by locale, a `LocalizedRecords<T, K>`: a field declared as `string` reads as a `Record<string, string>`. Calling `localize()` on that view with one locale gives back the row type. The view is not the row type, so pass the row itself to `insert()` or `update()`. Write all translations with `Object.assign`, which calls the field's setter. `undefined` or `null` clears every translation. Any other value that is not a record, such as a single text, throws a `TypeError` and leaves the stored translations as they were:
191
191
 
192
192
  ```typescript fragment
193
193
  // Get all translations
194
- const allNames: unknown = product.localize("*").name;
194
+ const allNames: Record<string, string> = product.localize("*", ["name"]).name;
195
195
  // { en: "Premium Headphones", fr: "Casque Premium", es: "Auriculares Premium" }
196
196
 
197
197
  // Set all translations
@@ -216,9 +216,12 @@ Object.assign(product.localize("*"), {
216
216
  The `LocalizationModifier` mixin adds a `localize` method:
217
217
 
218
218
  ```typescript fragment
219
- localize<This extends { constructor: any }>(this: This, locale: string, fields?: Array<keyof This>): This
219
+ localize<This extends { constructor: any }, K extends keyof This>(this: This, locale: "*", fields: readonly K[]): LocalizedRecords<This, K>
220
+ localize<This extends { constructor: any }>(this: This, locale: string, fields?: ReadonlyArray<keyof This>): This
220
221
  ```
221
222
 
223
+ Without `fields`, `localize("*")` unlocks every localized field but keeps the class's field types. A listed method stays callable.
224
+
222
225
  `localize()` changes the active locale and returns the same object, not a copy for that locale.
223
226
 
224
227
  - **References share state.** `spanish`, `german`, and `product` above refer to one object. A later `localize()` call changes what all three references read.
@@ -410,7 +413,7 @@ Base64 is an encoding, not encryption. The example declares its `LockedType` as
410
413
 
411
414
  ### `ContainerModifier`
412
415
 
413
- A modifier that stores several values under keys, as `LocalizationModifier` does, extends `ContainerModifier`. Its `lock`, `unlock`, and `unlockrequest` take the key as their argument. `lock` and `unlock` treat the wildcard key `"*"` as the whole record:
416
+ A modifier that stores several values under keys, as `LocalizationModifier` does, extends `ContainerModifier`. Its `lock`, `unlock`, and `unlockrequest` take the key as their argument, a `string` or `undefined`. `lock` and `unlock` treat the wildcard key `"*"` as the whole record, and `lock` throws a `TypeError` for a value under `"*"` that is not a record. `undefined` is no key: `lock` stores nothing under it, and `unlock` and `unlockrequest` find no value, so a `LocalizationModifier` answers its `fallbackLocale`:
414
417
 
415
418
  ```typescript
416
419
  import { ContainerModifier } from "@antelopejs/interface-database-decorators";
@@ -95,6 +95,8 @@ await userModel.update({ _id: "user-123", name: "Updated Name" });
95
95
 
96
96
  When you call `update` without a separate ID argument, the object must include the primary key. Its type requires `_id`, and a call without it throws `Missing primary key in object update.`
97
97
 
98
+ An update with plain data that sets a field whose modifiers lock without arguments, such as an encrypted or hashed field, first reads the row's stored modifier state, then locks the new value onto it. The row keeps its other modified fields and its hash salt. A table instance, such as one read with `get`, already holds that state and costs no extra read.
99
+
98
100
  ### Delete
99
101
 
100
102
  ```typescript fragment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-database-decorators",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "AntelopeJS decorators for database tables and typed data models, with encrypted, hashed and localized fields",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -128,7 +128,7 @@
128
128
  "test": "pnpm run build && ajs module test .",
129
129
  "format:check": "oxfmt --check .",
130
130
  "knip": "knip",
131
- "prepare": "if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1; then lefthook install; fi",
131
+ "prepare": "if [ -e .git ] && command -v git >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1 && GIT_DIR=.git GIT_WORK_TREE=. git rev-parse --git-dir >/dev/null 2>&1; then GIT_DIR=.git GIT_WORK_TREE=. lefthook install; fi",
132
132
  "checks": "antelopejs-checks",
133
133
  "size": "antelopejs-check-size",
134
134
  "sync": "antelopejs-sync",
@@ -142,11 +142,11 @@
142
142
  "reflect-metadata": "^0.2.2"
143
143
  },
144
144
  "devDependencies": {
145
- "@antelopejs/core": "^1.20.0",
145
+ "@antelopejs/core": "^1.21.0",
146
146
  "@antelopejs/interface-api": "^0.0.15",
147
147
  "@antelopejs/interface-core": "^0.4.0",
148
148
  "@antelopejs/interface-database": "^0.3.0",
149
- "@antelopejs/tooling-configs": "^0.2.17",
149
+ "@antelopejs/tooling-configs": "^0.4.0",
150
150
  "@types/chai": "^4.3.20",
151
151
  "@types/mocha": "^10.0.10",
152
152
  "@types/node": "^22.19.15",
@@ -129,7 +129,10 @@ class Chef extends Table.with(
129
129
  - `@Hashed({ algorithm })` stores a fast digest instead of scrypt: use it only for non-secret fingerprints, never for credentials.
130
130
  - Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`);
131
131
  values assigned before `localize()` are held in floating state and silently dropped on insert/update.
132
+ - `instance.localize("*", ["motto"])` reads and writes every locale at once, typed `Record<string, string>` for each listed field (`LocalizedRecords<T, K>`); without a field list the field types stay unchanged.
133
+ - Under `localize("*")` a localized field takes the record of texts by locale, and `undefined` or `null` clears every locale. A single text throws a `TypeError`, whether it is assigned or held from `fromPlainData` when `localize("*")` runs.
132
134
  - A localized value `""` is stored as no value: reads and queries in that locale get the `fallbackLocale` value.
135
+ - A `ContainerModifier` key of `undefined` is no key: writes store nothing under it, and reads and queries get no value, or the `fallbackLocale` value for a localized field.
133
136
  - `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by introspection tooling via `RelationStaticMetadata`.
134
137
  - `Model.validate` (and `{ validate: true }` on insert/update) only checks fields whose `@Field` type is an
135
138
  io-ts-style codec with `.decode`; plain string field tokens are skipped. Update validates with
@@ -144,7 +147,7 @@ class Chef extends Table.with(
144
147
  to the whole group.
145
148
  - `@Fixture` data is only inserted by `RegisterSchema` when the table is empty (count === 0).
146
149
  - `GetModel` caches by model class + instanceId; the same pair always returns the same instance.
147
- - `RegisterSchema` binds each registered table class to the `Schema` it builds; call it in the module that declares the tables. `User.table()`, `User.table(instanceId)` and `User.table(db)` then return the class's AQL table, and `GetModel` builds `BasicDataModel` models on that same schema, even when another module (bound to another database) calls them. An unbound class throws rather than looking a schema up by id.
150
+ - `RegisterSchema` binds each registered table class to the `Schema` it builds; call it in the module that declares the tables. `User.table()`, `User.table(instanceId)` and `User.table(db)` then return the class's AQL table, and `GetModel` builds `BasicDataModel` models on that same schema, even when another module (bound to another database) calls them. An unbound class whose schema name has a schema uses that schema; one whose schema name has none throws `<Class> is not bound to a schema`.
148
151
  - Serialization of modified instances to plain JSON goes through `toPlainData` (auto-attached as `toJSON`).
149
152
 
150
153
  Deeper reference: this package's `docs/` chapters — Introduction, Table Definitions, Table Modifiers,