@plasius/schema 1.0.9 β 1.0.11
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/.github/workflows/cd.yml +21 -3
- package/.vscode/launch.json +1 -1
- package/CHANGELOG.md +65 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/schema.ts +82 -13
package/.github/workflows/cd.yml
CHANGED
|
@@ -2,8 +2,6 @@ name: CD (Publish to npm)
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_dispatch:
|
|
5
|
-
release:
|
|
6
|
-
types: [published]
|
|
7
5
|
|
|
8
6
|
permissions:
|
|
9
7
|
contents: write
|
|
@@ -12,7 +10,7 @@ permissions:
|
|
|
12
10
|
jobs:
|
|
13
11
|
publish:
|
|
14
12
|
runs-on: ubuntu-latest
|
|
15
|
-
environment:
|
|
13
|
+
environment: production
|
|
16
14
|
steps:
|
|
17
15
|
- name: Checkout
|
|
18
16
|
uses: actions/checkout@v4
|
|
@@ -41,6 +39,11 @@ jobs:
|
|
|
41
39
|
echo "New version: $NEW_VER"
|
|
42
40
|
git push --follow-tags
|
|
43
41
|
|
|
42
|
+
# Expose tag (vX.Y.Z) and version (X.Y.Z) for later steps
|
|
43
|
+
VER_NO_V=${NEW_VER#v}
|
|
44
|
+
echo "tag=$NEW_VER" >> "$GITHUB_OUTPUT"
|
|
45
|
+
echo "version=$VER_NO_V" >> "$GITHUB_OUTPUT"
|
|
46
|
+
|
|
44
47
|
NAME=$(node -p "require('./package.json').name")
|
|
45
48
|
echo "name=$NAME" >> "$GITHUB_OUTPUT"
|
|
46
49
|
if npm view "$NAME" version >/dev/null 2>&1; then
|
|
@@ -49,6 +52,21 @@ jobs:
|
|
|
49
52
|
echo "flags=--access public" >> "$GITHUB_OUTPUT"
|
|
50
53
|
fi
|
|
51
54
|
|
|
55
|
+
- name: Create GitHub Release from tag (first-party)
|
|
56
|
+
env:
|
|
57
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
58
|
+
run: |
|
|
59
|
+
set -euo pipefail
|
|
60
|
+
TAG="${{ steps.pkg.outputs.tag }}"
|
|
61
|
+
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
62
|
+
echo "Release $TAG already exists; skipping creation."
|
|
63
|
+
else
|
|
64
|
+
gh release create "$TAG" \
|
|
65
|
+
--title "Release $TAG" \
|
|
66
|
+
--generate-notes \
|
|
67
|
+
--latest
|
|
68
|
+
fi
|
|
69
|
+
|
|
52
70
|
- name: Publish
|
|
53
71
|
env:
|
|
54
72
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.vscode/launch.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"request": "launch",
|
|
7
7
|
"name": "Debug Vitest",
|
|
8
8
|
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
|
|
9
|
-
"args": ["run"
|
|
9
|
+
"args": ["run"],
|
|
10
10
|
"cwd": "${workspaceFolder}",
|
|
11
11
|
"console": "integratedTerminal",
|
|
12
12
|
"skipFiles": ["<node_internals>/**"]
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
# Changelog
|
|
3
|
+
|
|
4
|
+
All notable changes to this project will be documented in this file.
|
|
5
|
+
|
|
6
|
+
The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/)**, and this project adheres to **[Semantic Versioning](https://semver.org/spec/v2.0.0.html)**.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
- **Added**
|
|
13
|
+
- (placeholder) Add new validators, field helpers, or PII utilities here.
|
|
14
|
+
|
|
15
|
+
- **Changed**
|
|
16
|
+
- ./src/schema.ts Added comments defining functionality on all externally facing functions.
|
|
17
|
+
|
|
18
|
+
- **Fixed**
|
|
19
|
+
- ./src/schema.ts Validation no longer mutates the input, internal system fields are set only on result if not previously present.
|
|
20
|
+
|
|
21
|
+
- **Security**
|
|
22
|
+
- (placeholder)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [1.0.0] - 2025-09-16
|
|
27
|
+
|
|
28
|
+
- **Added**
|
|
29
|
+
- Initial public release of `@plasius/schema`.
|
|
30
|
+
- Fluent field builder API: `field().string().required()`, `field().number().min()`, etc.
|
|
31
|
+
- Type inference utilities to derive TypeScript types from schema definitions.
|
|
32
|
+
- Built-in validators for common standards:
|
|
33
|
+
- ISO-3166 country codes
|
|
34
|
+
- ISO-4217 currency codes
|
|
35
|
+
- RFC 5322 email format
|
|
36
|
+
- E.164 phone format
|
|
37
|
+
- WHATWG URL format
|
|
38
|
+
- ISO 8601 date/time
|
|
39
|
+
- OWASP-guided text/name constraints
|
|
40
|
+
- UUID (RFC 4122) and SemVer 2.0.0
|
|
41
|
+
- PII annotations and helpers for redaction/masking before logging.
|
|
42
|
+
- Lightweight validation runner with success/error result types.
|
|
43
|
+
|
|
44
|
+
- **Changed**
|
|
45
|
+
- N/A (initial release)
|
|
46
|
+
|
|
47
|
+
- **Fixed**
|
|
48
|
+
- N/A (initial release)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Release process (maintainers)
|
|
53
|
+
|
|
54
|
+
1. Update `CHANGELOG.md` under **Unreleased** with userβvisible changes.
|
|
55
|
+
2. Bump version in `package.json` following SemVer (major/minor/patch).
|
|
56
|
+
3. Move entries from **Unreleased** to a new version section with the current date.
|
|
57
|
+
4. Tag the release in Git (`vX.Y.Z`) and push tags.
|
|
58
|
+
5. Publish to npm (via CI/CD or `npm publish`).
|
|
59
|
+
|
|
60
|
+
> Tip: Use Conventional Commits in PR titles/bodies to make changelog updates easier.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
[Unreleased]: https://github.com/Plasius-LTD/plasius-schema/compare/v1.0.0...HEAD
|
|
65
|
+
[1.0.0]: https://github.com/Plasius-LTD/plasius-schema/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ This ensures your local development environment matches the version used in CI/C
|
|
|
48
48
|
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
49
49
|
|
|
50
50
|
- [Code of Conduct](./CODE_OF_CONDUCT.md)
|
|
51
|
-
- [Contributor License Agreement](./CLA.md)
|
|
51
|
+
- [Contributor License Agreement](./legal/CLA.md)
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
package/package.json
CHANGED
package/src/schema.ts
CHANGED
|
@@ -500,15 +500,14 @@ export function createSchema<S extends SchemaShape>(
|
|
|
500
500
|
if (typeof input !== "object" || input === null) {
|
|
501
501
|
return { valid: false, errors: ["Input must be an object"] } as any;
|
|
502
502
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
503
|
+
// Work on a non-mutating copy that includes system defaults for first-time objects
|
|
504
|
+
const working: Record<string, any> = { ...(input as any) };
|
|
505
|
+
if (working.type == null) working.type = entityType;
|
|
506
|
+
if (working.version == null) working.version = version;
|
|
508
507
|
|
|
509
508
|
for (const key in schema._shape) {
|
|
510
509
|
const def = schema._shape[key];
|
|
511
|
-
const value =
|
|
510
|
+
const value = working[key];
|
|
512
511
|
|
|
513
512
|
if (!def) {
|
|
514
513
|
errors.push(`Field definition missing for: ${key}`);
|
|
@@ -553,6 +552,7 @@ export function createSchema<S extends SchemaShape>(
|
|
|
553
552
|
result[key] = value;
|
|
554
553
|
}
|
|
555
554
|
|
|
555
|
+
|
|
556
556
|
if (errors.length === 0 && options.schemaValidator) {
|
|
557
557
|
const castValue = result as Infer<S>;
|
|
558
558
|
if (!options.schemaValidator(castValue)) {
|
|
@@ -570,7 +570,28 @@ export function createSchema<S extends SchemaShape>(
|
|
|
570
570
|
// specific validator for a schema to allow conditional validation
|
|
571
571
|
schemaValidator: options.schemaValidator!, // <== expose it here!
|
|
572
572
|
|
|
573
|
-
|
|
573
|
+
/**
|
|
574
|
+
* Recursively validates entity references defined in this schema.
|
|
575
|
+
*
|
|
576
|
+
* Traverses fields of type `ref` and arrays of `ref` and resolves each target
|
|
577
|
+
* entity using the provided `resolveEntity` function. When `autoValidate` is
|
|
578
|
+
* enabled (default) and the field's `refPolicy` is `eager`, the referenced
|
|
579
|
+
* entity's schema is fetched and validated via `validateComposition` up to
|
|
580
|
+
* `maxDepth` levels.
|
|
581
|
+
*
|
|
582
|
+
* Skips fields not listed in `onlyFields` when provided. Prevents cycles via
|
|
583
|
+
* a `visited` set in `validatorContext`.
|
|
584
|
+
*
|
|
585
|
+
* @param entity The root entity to validate (must include `type` and `id`).
|
|
586
|
+
* @param options Options controlling traversal and resolution behavior.
|
|
587
|
+
* @param options.resolveEntity Function to resolve a referenced entity by type and id.
|
|
588
|
+
* @param options.validatorContext Internal context (visited set) to prevent cycles.
|
|
589
|
+
* @param options.maxDepth Maximum depth for recursive validation (default: 5).
|
|
590
|
+
* @param options.onlyFields Optional whitelist of field names to validate.
|
|
591
|
+
* @param options.log Optional logger for traversal/debug output.
|
|
592
|
+
*
|
|
593
|
+
* @throws Error if a broken reference is encountered (target cannot be resolved).
|
|
594
|
+
*/
|
|
574
595
|
async validateComposition(entity, options) {
|
|
575
596
|
const {
|
|
576
597
|
resolveEntity,
|
|
@@ -643,6 +664,11 @@ export function createSchema<S extends SchemaShape>(
|
|
|
643
664
|
}
|
|
644
665
|
},
|
|
645
666
|
|
|
667
|
+
/**
|
|
668
|
+
* Returns the configured table name for this schema.
|
|
669
|
+
*
|
|
670
|
+
* @throws Error if no store/table name has been defined for this schema.
|
|
671
|
+
*/
|
|
646
672
|
tableName(): string {
|
|
647
673
|
if (!store || store === "") {
|
|
648
674
|
throw new Error("Store is not defined for this schema");
|
|
@@ -650,7 +676,15 @@ export function createSchema<S extends SchemaShape>(
|
|
|
650
676
|
return store;
|
|
651
677
|
},
|
|
652
678
|
|
|
653
|
-
|
|
679
|
+
/**
|
|
680
|
+
* Transforms an input object for persistence by applying PII protection
|
|
681
|
+
* according to field annotations (e.g., encryption and hashing).
|
|
682
|
+
*
|
|
683
|
+
* @param input The raw entity data.
|
|
684
|
+
* @param encryptFn Function used to encrypt sensitive values.
|
|
685
|
+
* @param hashFn Function used to hash sensitive values.
|
|
686
|
+
* @returns A new object safe to store.
|
|
687
|
+
*/
|
|
654
688
|
prepareForStorage(
|
|
655
689
|
input: Record<string, any>,
|
|
656
690
|
encryptFn: (value: any) => string,
|
|
@@ -659,6 +693,14 @@ export function createSchema<S extends SchemaShape>(
|
|
|
659
693
|
return piiPrepareForStorage(_shape, input, encryptFn, hashFn);
|
|
660
694
|
},
|
|
661
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Reverses storage transformations for read paths (e.g., decrypts values)
|
|
698
|
+
* according to PII annotations, returning a consumer-friendly object.
|
|
699
|
+
*
|
|
700
|
+
* @param stored Data retrieved from storage.
|
|
701
|
+
* @param decryptFn Function used to decrypt values that were encrypted on write.
|
|
702
|
+
* @returns A new object suitable for application consumption.
|
|
703
|
+
*/
|
|
662
704
|
prepareForRead(
|
|
663
705
|
stored: Record<string, any>,
|
|
664
706
|
decryptFn: (value: string) => any
|
|
@@ -666,7 +708,14 @@ export function createSchema<S extends SchemaShape>(
|
|
|
666
708
|
return piiPrepareForRead(_shape, stored, decryptFn);
|
|
667
709
|
},
|
|
668
710
|
|
|
669
|
-
|
|
711
|
+
/**
|
|
712
|
+
* Produces a log-safe copy of the provided data by redacting or pseudonymizing
|
|
713
|
+
* PII fields in accordance with field annotations.
|
|
714
|
+
*
|
|
715
|
+
* @param data Arbitrary data to sanitize for logging.
|
|
716
|
+
* @param pseudonymFn Function producing stable pseudonyms for sensitive values.
|
|
717
|
+
* @returns A copy safe to emit to logs.
|
|
718
|
+
*/
|
|
670
719
|
sanitizeForLog(
|
|
671
720
|
data: Record<string, any>,
|
|
672
721
|
pseudonymFn: (value: any) => string
|
|
@@ -674,6 +723,10 @@ export function createSchema<S extends SchemaShape>(
|
|
|
674
723
|
return piiSanitizeForLog(_shape, data, pseudonymFn);
|
|
675
724
|
},
|
|
676
725
|
|
|
726
|
+
/**
|
|
727
|
+
* Returns a list of fields annotated with PII metadata for auditing purposes.
|
|
728
|
+
* Each entry includes classification, required action, and optional log policy.
|
|
729
|
+
*/
|
|
677
730
|
getPiiAudit(): Array<{
|
|
678
731
|
field: string;
|
|
679
732
|
classification: PIIClassification;
|
|
@@ -684,10 +737,21 @@ export function createSchema<S extends SchemaShape>(
|
|
|
684
737
|
return piiGetPiiAudit(_shape);
|
|
685
738
|
},
|
|
686
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Produces a copy of stored data suitable for data deletion flows by scrubbing
|
|
742
|
+
* or blanking PII per field annotations.
|
|
743
|
+
*
|
|
744
|
+
* @param stored Data as persisted.
|
|
745
|
+
* @returns A copy with PII removed or neutralized for deletion.
|
|
746
|
+
*/
|
|
687
747
|
scrubPiiForDelete(stored: Record<string, any>): Record<string, any> {
|
|
688
748
|
return piiScrubPiiForDelete(_shape, stored);
|
|
689
749
|
},
|
|
690
750
|
|
|
751
|
+
/**
|
|
752
|
+
* Returns a normalized description of the schema suitable for documentation
|
|
753
|
+
* or UI rendering (type, optionality, enum values, PII flags, etc.).
|
|
754
|
+
*/
|
|
691
755
|
describe() {
|
|
692
756
|
const description: Record<string, any> = {};
|
|
693
757
|
for (const [key, def] of Object.entries(schema._shape)) {
|
|
@@ -715,19 +779,24 @@ export function createSchema<S extends SchemaShape>(
|
|
|
715
779
|
return schema;
|
|
716
780
|
}
|
|
717
781
|
|
|
718
|
-
|
|
782
|
+
/**
|
|
783
|
+
* Retrieves a previously registered schema by its `entityType` from the
|
|
784
|
+
* in-process global schema registry.
|
|
785
|
+
*/
|
|
719
786
|
export function getSchemaForType(type: string): Schema<any> | undefined {
|
|
720
787
|
return globalSchemaRegistry.get(type);
|
|
721
788
|
}
|
|
722
789
|
|
|
723
|
-
|
|
790
|
+
/**
|
|
791
|
+
* Returns all schemas registered in the in-process global registry.
|
|
792
|
+
*/
|
|
724
793
|
export function getAllSchemas(): Schema<any>[] {
|
|
725
794
|
return Array.from(globalSchemaRegistry.values());
|
|
726
795
|
}
|
|
727
796
|
|
|
728
797
|
/**
|
|
729
|
-
* Renders a schema
|
|
730
|
-
*
|
|
798
|
+
* Renders a schema into a simplified descriptor for front-end consumption.
|
|
799
|
+
* Intended for documentation and admin tooling rather than validation.
|
|
731
800
|
*/
|
|
732
801
|
export function renderSchemaDescription(
|
|
733
802
|
schema: Schema<any>
|