@finsys/core 2.6.0 → 3.1.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 +14 -2
- package/dist/data/adapter-categories.json +142 -2
- package/dist/index.cjs +282 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +207 -23
- package/dist/index.d.ts +207 -23
- package/dist/index.js +279 -32
- package/dist/index.js.map +1 -1
- package/dist/schema/adapter-manifest.schema.json +13 -2
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -571,16 +571,28 @@ interface ExtractionStatusResult {
|
|
|
571
571
|
declare function resolveExtractionStatus(ihsRecord: Record<string, unknown>, jobRecords?: ExtractionJobRecord[]): ExtractionStatusResult;
|
|
572
572
|
|
|
573
573
|
/**
|
|
574
|
-
*
|
|
575
|
-
* so consumers get TypeScript autocomplete (`'telco-carrier' |
|
|
576
|
-
* 'payment-network' | ...`) and a compile-time error if they reference
|
|
577
|
-
* a category that doesn't exist.
|
|
574
|
+
* Adapter category identifier.
|
|
578
575
|
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
576
|
+
* Open `string` type (SYS-2500). The authoritative set lives in the
|
|
577
|
+
* runtime registry built from `data/adapter-categories.json`; there is
|
|
578
|
+
* no compile-time union to enumerate. The trade-off vs. the old
|
|
579
|
+
* literal union is the loss of autocomplete + exhaustiveness on
|
|
580
|
+
* category ids — validate at boundaries with `isAdapterCategory()` /
|
|
581
|
+
* `assertAdapterCategory()` instead, and read the canonical set from
|
|
582
|
+
* `ADAPTER_CATEGORY_IDS` / `allCategories()`.
|
|
582
583
|
*/
|
|
583
|
-
type AdapterCategory =
|
|
584
|
+
type AdapterCategory = string;
|
|
585
|
+
/**
|
|
586
|
+
* Union of all canonical field names declared by any category. Loose-
|
|
587
|
+
* typed (string) because the set grows with each category-added minor;
|
|
588
|
+
* the runtime registry narrows against the data file.
|
|
589
|
+
*
|
|
590
|
+
* Adapter `produces` lists are typed as ReadonlyArray<CanonicalFieldName>
|
|
591
|
+
* — the host validates membership against the adapter's category at
|
|
592
|
+
* registration time, refusing adapters that promise fields outside
|
|
593
|
+
* their category.
|
|
594
|
+
*/
|
|
595
|
+
type CanonicalFieldName = string;
|
|
584
596
|
/**
|
|
585
597
|
* Per-field metadata for a canonical field declared by a category.
|
|
586
598
|
* Frozen at module load — the data file is authoritative.
|
|
@@ -607,20 +619,14 @@ interface CategorySchema {
|
|
|
607
619
|
readonly fields: ReadonlyArray<CanonicalFieldSpec>;
|
|
608
620
|
}
|
|
609
621
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* Adapter `produces` lists are typed as ReadonlyArray<CanonicalFieldName>
|
|
616
|
-
* — the host validates membership against the adapter's category at
|
|
617
|
-
* registration time, refusing adapters that promise fields outside
|
|
618
|
-
* their category.
|
|
622
|
+
* Every category id declared by this version of finsys-core, in
|
|
623
|
+
* data-file order. The runtime equivalent of the old hardcoded union —
|
|
624
|
+
* derived from the single source of truth rather than maintained by
|
|
625
|
+
* hand. Treat order as unstable across versions.
|
|
619
626
|
*/
|
|
620
|
-
|
|
627
|
+
declare const ADAPTER_CATEGORY_IDS: ReadonlyArray<AdapterCategory>;
|
|
621
628
|
/**
|
|
622
|
-
* Look up a category schema by id. Throws if the id is unknown
|
|
623
|
-
* is a programmer error (the type system should prevent it).
|
|
629
|
+
* Look up a category schema by id. Throws if the id is unknown.
|
|
624
630
|
*/
|
|
625
631
|
declare function categorySchemaOf(id: AdapterCategory): CategorySchema;
|
|
626
632
|
/**
|
|
@@ -640,9 +646,25 @@ declare function allCategories(): ReadonlyArray<CategorySchema>;
|
|
|
640
646
|
* Returns null if the field name isn't declared by any category in
|
|
641
647
|
* this version of finsys-core. Useful when the host app is reading
|
|
642
648
|
* canonical field values back from storage + wants to identify the
|
|
643
|
-
* producing category for rendering.
|
|
649
|
+
* producing category for rendering. O(1).
|
|
644
650
|
*/
|
|
645
651
|
declare function categoryForField(field: CanonicalFieldName): AdapterCategory | null;
|
|
652
|
+
/**
|
|
653
|
+
* Runtime membership check — is `id` a category declared by this
|
|
654
|
+
* version of finsys-core? Use this at trust boundaries (parsing a
|
|
655
|
+
* manifest, validating an API parameter) now that `AdapterCategory` is
|
|
656
|
+
* an open `string` and the compiler can no longer reject unknown ids.
|
|
657
|
+
*/
|
|
658
|
+
declare function isAdapterCategory(id: string): boolean;
|
|
659
|
+
/**
|
|
660
|
+
* Assert membership: returns `id` if it names a declared category,
|
|
661
|
+
* throws otherwise. Note this is a RUNTIME guard only — `AdapterCategory`
|
|
662
|
+
* is an open `string`, so there is no type-level narrowing to apply.
|
|
663
|
+
* The companion to `isAdapterCategory` for call sites that want a hard
|
|
664
|
+
* failure (e.g. the host rejecting a manifest whose category isn't in
|
|
665
|
+
* this finsys-core version's catalogue).
|
|
666
|
+
*/
|
|
667
|
+
declare function assertAdapterCategory(id: string): AdapterCategory;
|
|
646
668
|
|
|
647
669
|
/**
|
|
648
670
|
* Source Adapter contract — the framework for ingesting unstructured
|
|
@@ -744,8 +766,26 @@ type CanonicalFieldValue = number | string | boolean | null;
|
|
|
744
766
|
* host app's discovery mechanism (SYS-2444); the host app constructs
|
|
745
767
|
* the adapter from its manifest + extract module, then registers it
|
|
746
768
|
* against the registry by `id`.
|
|
769
|
+
*
|
|
770
|
+
* Type parameter `E` lets adapter authors declare the partner-specific
|
|
771
|
+
* extension shape on ApplicantIdentity. v2.7.0+ — defaults to `{}` so
|
|
772
|
+
* v2.6.0 adapters (which didn't have fetch and never read identity)
|
|
773
|
+
* stay assignment-compatible. A telco adapter that needs an MSISDN
|
|
774
|
+
* declares it once at the interface level:
|
|
775
|
+
*
|
|
776
|
+
* interface CelcomExt { msisdn: string }
|
|
777
|
+
* const celcomTelco: SourceAdapter<CelcomExt> = {
|
|
778
|
+
* async fetch(identity) {
|
|
779
|
+
* identity.msisdn // string (typed via E)
|
|
780
|
+
* identity.ic // string (core)
|
|
781
|
+
* },
|
|
782
|
+
* ...
|
|
783
|
+
* }
|
|
784
|
+
*
|
|
785
|
+
* The generic flows from interface → method, so implementations
|
|
786
|
+
* don't have to redeclare it on fetch.
|
|
747
787
|
*/
|
|
748
|
-
interface SourceAdapter {
|
|
788
|
+
interface SourceAdapter<E extends Record<string, unknown> = {}> {
|
|
749
789
|
/**
|
|
750
790
|
* Globally unique id for this adapter instance. By convention:
|
|
751
791
|
* `<vendor>-<category-short>-v<n>` — e.g. `celcom-telco-v1`. Vendor
|
|
@@ -803,7 +843,129 @@ interface SourceAdapter {
|
|
|
803
843
|
* expected-failure path.
|
|
804
844
|
*/
|
|
805
845
|
extract(raw: RawPayload): Promise<AdapterExtraction[]>;
|
|
846
|
+
/**
|
|
847
|
+
* Optional partner-data fetch path. v2.7.0+ — adapters that own their
|
|
848
|
+
* partner-API integration declare this method; the host invokes it
|
|
849
|
+
* before extract() and passes the result through. Adapters that
|
|
850
|
+
* receive raw payload from elsewhere (e.g., bank-statement adapters
|
|
851
|
+
* reading FinXtract OCR output, or webhook-driven push ingest) omit
|
|
852
|
+
* fetch() entirely and the host treats raw as already-staged.
|
|
853
|
+
*
|
|
854
|
+
* The host builds `identity` from the IHS row (ic, fullName, etc.)
|
|
855
|
+
* plus any partner-specific fields the adapter declared via the
|
|
856
|
+
* manifest's `requiredIdentityFields`. If a required field is missing
|
|
857
|
+
* from the IHS, the host logs + skips this adapter for the run
|
|
858
|
+
* (no extract call, no canonical rows).
|
|
859
|
+
*
|
|
860
|
+
* Implementations should:
|
|
861
|
+
* - Throw AdapterError('source_unavailable') ONLY on transient,
|
|
862
|
+
* retryable upstream failures (timeouts, 5xx, rate limits). The
|
|
863
|
+
* host runner is free to retry these; misclassifying a permanent
|
|
864
|
+
* failure here will burn retry budget for nothing.
|
|
865
|
+
* - Throw AdapterError('not_applicable') on permanent
|
|
866
|
+
* no-relationship signals — 404 ("applicant unknown to partner"),
|
|
867
|
+
* 410, an explicit "not a subscriber" response. Communicates
|
|
868
|
+
* 'we asked and they said no' so the host records the run + skips
|
|
869
|
+
* retry. This is the correct reason for most non-retryable
|
|
870
|
+
* upstream conditions.
|
|
871
|
+
* - Throw AdapterError('payload_invalid') if the partner returned
|
|
872
|
+
* a malformed response (200 with garbage body, schema violation).
|
|
873
|
+
* - Return a RawPayload that extract() will translate. The same
|
|
874
|
+
* adapter's extract() is the only consumer; the host doesn't
|
|
875
|
+
* inspect the shape.
|
|
876
|
+
*
|
|
877
|
+
* Strict additive — v2.6.0 read-only adapters that omit fetch()
|
|
878
|
+
* keep working without change.
|
|
879
|
+
*
|
|
880
|
+
* @example WRONG — recurses into the adapter method:
|
|
881
|
+
* ```ts
|
|
882
|
+
* const adapter: SourceAdapter = {
|
|
883
|
+
* async fetch(identity) {
|
|
884
|
+
* const res = await fetch(`/api/lookup/${identity.ic}`) // infinite recursion
|
|
885
|
+
* return res.json()
|
|
886
|
+
* },
|
|
887
|
+
* ...
|
|
888
|
+
* }
|
|
889
|
+
* ```
|
|
890
|
+
*
|
|
891
|
+
* @example RIGHT — explicit globalThis access:
|
|
892
|
+
* ```ts
|
|
893
|
+
* const adapter: SourceAdapter = {
|
|
894
|
+
* async fetch(identity) {
|
|
895
|
+
* const res = await globalThis.fetch(`/api/lookup/${identity.ic}`)
|
|
896
|
+
* return res.json()
|
|
897
|
+
* },
|
|
898
|
+
* ...
|
|
899
|
+
* }
|
|
900
|
+
* ```
|
|
901
|
+
*
|
|
902
|
+
* Implementation note: this method is named `fetch` which shadows
|
|
903
|
+
* the global `fetch()` inside the method body — see the @example
|
|
904
|
+
* blocks above. The fake-* reference adapters in
|
|
905
|
+
* @finsys/adapter-toolkit use the explicit-globalThis pattern.
|
|
906
|
+
* Partner repos can also pin this with the lint rule
|
|
907
|
+
* `no-restricted-syntax`: `CallExpression[callee.name='fetch']`
|
|
908
|
+
* inside any method literal named `fetch`.
|
|
909
|
+
*/
|
|
910
|
+
fetch?(identity: ApplicantIdentity<E>): Promise<RawPayload>;
|
|
806
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* Identity payload the host hands to fetch() so adapters can call
|
|
914
|
+
* partner APIs with the right per-applicant identifiers.
|
|
915
|
+
*
|
|
916
|
+
* The shape is an intersection of three pieces:
|
|
917
|
+
* - Core fields (`ihsId`, `ic`, `fullName`) — always present, the
|
|
918
|
+
* host populates them from the IHS row before invoking fetch().
|
|
919
|
+
* - Partner extensions (`E`) — typed declaratively per adapter.
|
|
920
|
+
* Defaults to `{}` (no extra typed fields) for adapters that only
|
|
921
|
+
* need the core trio.
|
|
922
|
+
* - Open index signature (`[k: string]: unknown`) — catches any
|
|
923
|
+
* ad-hoc field a host might pass through; reads land on `unknown`
|
|
924
|
+
* so partners must validate before use.
|
|
925
|
+
*
|
|
926
|
+
* Narrowing example (the ergonomics win this type is designed to deliver):
|
|
927
|
+
*
|
|
928
|
+
* interface CelcomExt { msisdn: string; accountRef: string }
|
|
929
|
+
*
|
|
930
|
+
* // declare adapter with the extension shape baked in:
|
|
931
|
+
* const celcom: SourceAdapter<CelcomExt> = {
|
|
932
|
+
* async fetch(identity) {
|
|
933
|
+
* identity.ic // string
|
|
934
|
+
* identity.msisdn // string (NOT unknown — narrowed by E)
|
|
935
|
+
* identity.foo // unknown (falls through to index signature)
|
|
936
|
+
* // ...
|
|
937
|
+
* },
|
|
938
|
+
* // ...
|
|
939
|
+
* }
|
|
940
|
+
*
|
|
941
|
+
* The intersection puts narrowed members at the root, so partners get
|
|
942
|
+
* `identity.msisdn` (not `identity.extensions?.msisdn`) — matching the
|
|
943
|
+
* pattern the host actually uses (it spreads partner-required fields
|
|
944
|
+
* onto the root identity object, no nested 'extensions' envelope).
|
|
945
|
+
*
|
|
946
|
+
* Examples:
|
|
947
|
+
* - Telco adapter declares `requiredIdentityFields: ['msisdn']`.
|
|
948
|
+
* Identity arrives with msisdn populated; adapter calls partner API.
|
|
949
|
+
* (Don't include 'ihsId', 'ic', or 'fullName' in
|
|
950
|
+
* `requiredIdentityFields` — those are core fields. `ic` in
|
|
951
|
+
* particular can be legitimately empty for non-MY scope, so
|
|
952
|
+
* declaring it required would cause the host to skip every applicant.)
|
|
953
|
+
* - Payment-network adapter declares `requiredIdentityFields:
|
|
954
|
+
* ['businessRegistrationNumber']`. Adapter looks it up + calls.
|
|
955
|
+
* - An adapter that only needs the IHS id declares no extra
|
|
956
|
+
* fields; identity has just the core trio.
|
|
957
|
+
*/
|
|
958
|
+
type ApplicantIdentity<E extends Record<string, unknown> = {}> = {
|
|
959
|
+
/** Internal IHS id — always present. */
|
|
960
|
+
readonly ihsId: number;
|
|
961
|
+
/** Malaysian IC (or analogous national id). May be empty for non-MY scope. */
|
|
962
|
+
readonly ic: string;
|
|
963
|
+
/** Applicant full legal name. */
|
|
964
|
+
readonly fullName: string;
|
|
965
|
+
} & E & {
|
|
966
|
+
/** Catch-all for ad-hoc reads (typed `unknown` — validate before use). */
|
|
967
|
+
readonly [key: string]: unknown;
|
|
968
|
+
};
|
|
807
969
|
/**
|
|
808
970
|
* Typed error thrown by adapters on expected failure paths. The
|
|
809
971
|
* `reason` discriminator lets the host app's adapter runner classify
|
|
@@ -984,6 +1146,28 @@ interface AdapterManifest {
|
|
|
984
1146
|
* host imports it dynamically.
|
|
985
1147
|
*/
|
|
986
1148
|
readonly implementation: DeclarativeImplementation | TypescriptImplementation;
|
|
1149
|
+
/**
|
|
1150
|
+
* v2.7.0 — partner-specific identity fields the adapter needs from
|
|
1151
|
+
* the IHS row when fetch() is invoked. Strings name the keys that
|
|
1152
|
+
* MUST be present on the ApplicantIdentity payload — the host
|
|
1153
|
+
* validates per-applicant before calling fetch() and skips the
|
|
1154
|
+
* adapter (with a logged warning) if any required field is missing.
|
|
1155
|
+
*
|
|
1156
|
+
* Examples:
|
|
1157
|
+
* - Telco adapter: `["ic", "msisdn"]`
|
|
1158
|
+
* - Payment-network adapter: `["businessRegistrationNumber"]`
|
|
1159
|
+
* - Bank-statement-from-OCR adapter: `[]` (no fetch — extract
|
|
1160
|
+
* reads from FinXtract output staged elsewhere)
|
|
1161
|
+
*
|
|
1162
|
+
* Omit entirely (or use empty array) for adapters that don't
|
|
1163
|
+
* implement fetch() — the host then passes an empty raw payload
|
|
1164
|
+
* to extract() and the adapter is responsible for sourcing data
|
|
1165
|
+
* via some other path (FinXtract output, webhook ingest, etc.).
|
|
1166
|
+
*
|
|
1167
|
+
* Core identity fields (`ihsId`, `ic`, `fullName`) are always
|
|
1168
|
+
* present — declaring them is harmless but unnecessary.
|
|
1169
|
+
*/
|
|
1170
|
+
readonly requiredIdentityFields?: ReadonlyArray<string>;
|
|
987
1171
|
/**
|
|
988
1172
|
* Optional free-form notes — useful for partner-side documentation
|
|
989
1173
|
* (where to find the adapter's source, who owns it, what version of
|
|
@@ -1036,4 +1220,4 @@ interface FieldMapEntry {
|
|
|
1036
1220
|
readonly transform?: "identity" | "pct_to_ratio01" | "to_boolean" | "to_integer";
|
|
1037
1221
|
}
|
|
1038
1222
|
|
|
1039
|
-
export { ALL_AGGREGATION_OPS, type AdapterCategory, AdapterError, type AdapterErrorReason, type AdapterExtraction, type AdapterManifest, type AggregationOp, BASE_FIELD_SPECS, BasicFormField, type CanonicalFieldName, type CanonicalFieldSpec, type CanonicalFieldValue, type CanonicalFieldValues, type Category, type CategorySchema, type Choice, DEFAULT_VALIDATOR_DEFINITIONS, type DeclarativeImplementation, type DocExtractionResult, DocExtractionStatus, type DropdownOption, type EditorValidator, ExtractionFileType, type ExtractionJobRecord, ExtractionJobStatus, type ExtractionStatusResult, FIELD_TYPE_DEFINITIONS, type FieldData, type FieldGroup, type FieldMapEntry, type FieldReference, FieldType, type FileFieldTableData, type FileFieldTableItem, FileFieldTableType, FileFormField, FormField, FormFieldCategory, type FormFieldInputType, type FormFieldType, type FormFieldTypeDefinitions, FormFieldValidator, FormSpec, type FormValidatorDefinitions, IHS_FAILURE_STATUSES, IHS_TERMINAL_STATUSES, IHS_VALID_STATUSES, type IhsDetailCategory, type IhsFieldDetail, IhsStatus, IhsValueFormat, type InstanceValue, type PageConfig, type RHFSchemaOutput, type RHFStep, type RawPayload, type ResolvedField, Role, type SourceAdapter, type SurveyElementJSON, type SurveyJSON, type SurveyPageJSON, type TypescriptImplementation, type UnifiedFormConfig, type Validator, allCategories, applyAggregation, applyDynamicTitles, buildFileFieldTables, categoryFieldsOf, categoryForField, categorySchemaOf, evaluateExpression, extractTimePeriods, generateRHFSchema, generateSurveyJson, getBaseCategories, getBaseFieldNames, getBaseFieldSpecMap, getBaseFieldSpecs, getCategoryName, getDisplayName, getDisplayNames, getGroupDisplayNames, getPastMonthLabel, getPastYearLabel, getStepDefaultValues, getStepSchema, groupColumnsByTimePeriod, groupDetailsByCategory, groupFieldsByCategory, groupFieldsByPattern, isFailureIhsStatus, isTerminalIhsStatus, isValidIhsStatus, processIhsDetails, resolveExtractionStatus, resolvePageFields, validateFormConfig, validateFormSpec, validatePagesConfig };
|
|
1223
|
+
export { ADAPTER_CATEGORY_IDS, ALL_AGGREGATION_OPS, type AdapterCategory, AdapterError, type AdapterErrorReason, type AdapterExtraction, type AdapterManifest, type AggregationOp, type ApplicantIdentity, BASE_FIELD_SPECS, BasicFormField, type CanonicalFieldName, type CanonicalFieldSpec, type CanonicalFieldValue, type CanonicalFieldValues, type Category, type CategorySchema, type Choice, DEFAULT_VALIDATOR_DEFINITIONS, type DeclarativeImplementation, type DocExtractionResult, DocExtractionStatus, type DropdownOption, type EditorValidator, ExtractionFileType, type ExtractionJobRecord, ExtractionJobStatus, type ExtractionStatusResult, FIELD_TYPE_DEFINITIONS, type FieldData, type FieldGroup, type FieldMapEntry, type FieldReference, FieldType, type FileFieldTableData, type FileFieldTableItem, FileFieldTableType, FileFormField, FormField, FormFieldCategory, type FormFieldInputType, type FormFieldType, type FormFieldTypeDefinitions, FormFieldValidator, FormSpec, type FormValidatorDefinitions, IHS_FAILURE_STATUSES, IHS_TERMINAL_STATUSES, IHS_VALID_STATUSES, type IhsDetailCategory, type IhsFieldDetail, IhsStatus, IhsValueFormat, type InstanceValue, type PageConfig, type RHFSchemaOutput, type RHFStep, type RawPayload, type ResolvedField, Role, type SourceAdapter, type SurveyElementJSON, type SurveyJSON, type SurveyPageJSON, type TypescriptImplementation, type UnifiedFormConfig, type Validator, allCategories, applyAggregation, applyDynamicTitles, assertAdapterCategory, buildFileFieldTables, categoryFieldsOf, categoryForField, categorySchemaOf, evaluateExpression, extractTimePeriods, generateRHFSchema, generateSurveyJson, getBaseCategories, getBaseFieldNames, getBaseFieldSpecMap, getBaseFieldSpecs, getCategoryName, getDisplayName, getDisplayNames, getGroupDisplayNames, getPastMonthLabel, getPastYearLabel, getStepDefaultValues, getStepSchema, groupColumnsByTimePeriod, groupDetailsByCategory, groupFieldsByCategory, groupFieldsByPattern, isAdapterCategory, isFailureIhsStatus, isTerminalIhsStatus, isValidIhsStatus, processIhsDetails, resolveExtractionStatus, resolvePageFields, validateFormConfig, validateFormSpec, validatePagesConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -571,16 +571,28 @@ interface ExtractionStatusResult {
|
|
|
571
571
|
declare function resolveExtractionStatus(ihsRecord: Record<string, unknown>, jobRecords?: ExtractionJobRecord[]): ExtractionStatusResult;
|
|
572
572
|
|
|
573
573
|
/**
|
|
574
|
-
*
|
|
575
|
-
* so consumers get TypeScript autocomplete (`'telco-carrier' |
|
|
576
|
-
* 'payment-network' | ...`) and a compile-time error if they reference
|
|
577
|
-
* a category that doesn't exist.
|
|
574
|
+
* Adapter category identifier.
|
|
578
575
|
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
576
|
+
* Open `string` type (SYS-2500). The authoritative set lives in the
|
|
577
|
+
* runtime registry built from `data/adapter-categories.json`; there is
|
|
578
|
+
* no compile-time union to enumerate. The trade-off vs. the old
|
|
579
|
+
* literal union is the loss of autocomplete + exhaustiveness on
|
|
580
|
+
* category ids — validate at boundaries with `isAdapterCategory()` /
|
|
581
|
+
* `assertAdapterCategory()` instead, and read the canonical set from
|
|
582
|
+
* `ADAPTER_CATEGORY_IDS` / `allCategories()`.
|
|
582
583
|
*/
|
|
583
|
-
type AdapterCategory =
|
|
584
|
+
type AdapterCategory = string;
|
|
585
|
+
/**
|
|
586
|
+
* Union of all canonical field names declared by any category. Loose-
|
|
587
|
+
* typed (string) because the set grows with each category-added minor;
|
|
588
|
+
* the runtime registry narrows against the data file.
|
|
589
|
+
*
|
|
590
|
+
* Adapter `produces` lists are typed as ReadonlyArray<CanonicalFieldName>
|
|
591
|
+
* — the host validates membership against the adapter's category at
|
|
592
|
+
* registration time, refusing adapters that promise fields outside
|
|
593
|
+
* their category.
|
|
594
|
+
*/
|
|
595
|
+
type CanonicalFieldName = string;
|
|
584
596
|
/**
|
|
585
597
|
* Per-field metadata for a canonical field declared by a category.
|
|
586
598
|
* Frozen at module load — the data file is authoritative.
|
|
@@ -607,20 +619,14 @@ interface CategorySchema {
|
|
|
607
619
|
readonly fields: ReadonlyArray<CanonicalFieldSpec>;
|
|
608
620
|
}
|
|
609
621
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* Adapter `produces` lists are typed as ReadonlyArray<CanonicalFieldName>
|
|
616
|
-
* — the host validates membership against the adapter's category at
|
|
617
|
-
* registration time, refusing adapters that promise fields outside
|
|
618
|
-
* their category.
|
|
622
|
+
* Every category id declared by this version of finsys-core, in
|
|
623
|
+
* data-file order. The runtime equivalent of the old hardcoded union —
|
|
624
|
+
* derived from the single source of truth rather than maintained by
|
|
625
|
+
* hand. Treat order as unstable across versions.
|
|
619
626
|
*/
|
|
620
|
-
|
|
627
|
+
declare const ADAPTER_CATEGORY_IDS: ReadonlyArray<AdapterCategory>;
|
|
621
628
|
/**
|
|
622
|
-
* Look up a category schema by id. Throws if the id is unknown
|
|
623
|
-
* is a programmer error (the type system should prevent it).
|
|
629
|
+
* Look up a category schema by id. Throws if the id is unknown.
|
|
624
630
|
*/
|
|
625
631
|
declare function categorySchemaOf(id: AdapterCategory): CategorySchema;
|
|
626
632
|
/**
|
|
@@ -640,9 +646,25 @@ declare function allCategories(): ReadonlyArray<CategorySchema>;
|
|
|
640
646
|
* Returns null if the field name isn't declared by any category in
|
|
641
647
|
* this version of finsys-core. Useful when the host app is reading
|
|
642
648
|
* canonical field values back from storage + wants to identify the
|
|
643
|
-
* producing category for rendering.
|
|
649
|
+
* producing category for rendering. O(1).
|
|
644
650
|
*/
|
|
645
651
|
declare function categoryForField(field: CanonicalFieldName): AdapterCategory | null;
|
|
652
|
+
/**
|
|
653
|
+
* Runtime membership check — is `id` a category declared by this
|
|
654
|
+
* version of finsys-core? Use this at trust boundaries (parsing a
|
|
655
|
+
* manifest, validating an API parameter) now that `AdapterCategory` is
|
|
656
|
+
* an open `string` and the compiler can no longer reject unknown ids.
|
|
657
|
+
*/
|
|
658
|
+
declare function isAdapterCategory(id: string): boolean;
|
|
659
|
+
/**
|
|
660
|
+
* Assert membership: returns `id` if it names a declared category,
|
|
661
|
+
* throws otherwise. Note this is a RUNTIME guard only — `AdapterCategory`
|
|
662
|
+
* is an open `string`, so there is no type-level narrowing to apply.
|
|
663
|
+
* The companion to `isAdapterCategory` for call sites that want a hard
|
|
664
|
+
* failure (e.g. the host rejecting a manifest whose category isn't in
|
|
665
|
+
* this finsys-core version's catalogue).
|
|
666
|
+
*/
|
|
667
|
+
declare function assertAdapterCategory(id: string): AdapterCategory;
|
|
646
668
|
|
|
647
669
|
/**
|
|
648
670
|
* Source Adapter contract — the framework for ingesting unstructured
|
|
@@ -744,8 +766,26 @@ type CanonicalFieldValue = number | string | boolean | null;
|
|
|
744
766
|
* host app's discovery mechanism (SYS-2444); the host app constructs
|
|
745
767
|
* the adapter from its manifest + extract module, then registers it
|
|
746
768
|
* against the registry by `id`.
|
|
769
|
+
*
|
|
770
|
+
* Type parameter `E` lets adapter authors declare the partner-specific
|
|
771
|
+
* extension shape on ApplicantIdentity. v2.7.0+ — defaults to `{}` so
|
|
772
|
+
* v2.6.0 adapters (which didn't have fetch and never read identity)
|
|
773
|
+
* stay assignment-compatible. A telco adapter that needs an MSISDN
|
|
774
|
+
* declares it once at the interface level:
|
|
775
|
+
*
|
|
776
|
+
* interface CelcomExt { msisdn: string }
|
|
777
|
+
* const celcomTelco: SourceAdapter<CelcomExt> = {
|
|
778
|
+
* async fetch(identity) {
|
|
779
|
+
* identity.msisdn // string (typed via E)
|
|
780
|
+
* identity.ic // string (core)
|
|
781
|
+
* },
|
|
782
|
+
* ...
|
|
783
|
+
* }
|
|
784
|
+
*
|
|
785
|
+
* The generic flows from interface → method, so implementations
|
|
786
|
+
* don't have to redeclare it on fetch.
|
|
747
787
|
*/
|
|
748
|
-
interface SourceAdapter {
|
|
788
|
+
interface SourceAdapter<E extends Record<string, unknown> = {}> {
|
|
749
789
|
/**
|
|
750
790
|
* Globally unique id for this adapter instance. By convention:
|
|
751
791
|
* `<vendor>-<category-short>-v<n>` — e.g. `celcom-telco-v1`. Vendor
|
|
@@ -803,7 +843,129 @@ interface SourceAdapter {
|
|
|
803
843
|
* expected-failure path.
|
|
804
844
|
*/
|
|
805
845
|
extract(raw: RawPayload): Promise<AdapterExtraction[]>;
|
|
846
|
+
/**
|
|
847
|
+
* Optional partner-data fetch path. v2.7.0+ — adapters that own their
|
|
848
|
+
* partner-API integration declare this method; the host invokes it
|
|
849
|
+
* before extract() and passes the result through. Adapters that
|
|
850
|
+
* receive raw payload from elsewhere (e.g., bank-statement adapters
|
|
851
|
+
* reading FinXtract OCR output, or webhook-driven push ingest) omit
|
|
852
|
+
* fetch() entirely and the host treats raw as already-staged.
|
|
853
|
+
*
|
|
854
|
+
* The host builds `identity` from the IHS row (ic, fullName, etc.)
|
|
855
|
+
* plus any partner-specific fields the adapter declared via the
|
|
856
|
+
* manifest's `requiredIdentityFields`. If a required field is missing
|
|
857
|
+
* from the IHS, the host logs + skips this adapter for the run
|
|
858
|
+
* (no extract call, no canonical rows).
|
|
859
|
+
*
|
|
860
|
+
* Implementations should:
|
|
861
|
+
* - Throw AdapterError('source_unavailable') ONLY on transient,
|
|
862
|
+
* retryable upstream failures (timeouts, 5xx, rate limits). The
|
|
863
|
+
* host runner is free to retry these; misclassifying a permanent
|
|
864
|
+
* failure here will burn retry budget for nothing.
|
|
865
|
+
* - Throw AdapterError('not_applicable') on permanent
|
|
866
|
+
* no-relationship signals — 404 ("applicant unknown to partner"),
|
|
867
|
+
* 410, an explicit "not a subscriber" response. Communicates
|
|
868
|
+
* 'we asked and they said no' so the host records the run + skips
|
|
869
|
+
* retry. This is the correct reason for most non-retryable
|
|
870
|
+
* upstream conditions.
|
|
871
|
+
* - Throw AdapterError('payload_invalid') if the partner returned
|
|
872
|
+
* a malformed response (200 with garbage body, schema violation).
|
|
873
|
+
* - Return a RawPayload that extract() will translate. The same
|
|
874
|
+
* adapter's extract() is the only consumer; the host doesn't
|
|
875
|
+
* inspect the shape.
|
|
876
|
+
*
|
|
877
|
+
* Strict additive — v2.6.0 read-only adapters that omit fetch()
|
|
878
|
+
* keep working without change.
|
|
879
|
+
*
|
|
880
|
+
* @example WRONG — recurses into the adapter method:
|
|
881
|
+
* ```ts
|
|
882
|
+
* const adapter: SourceAdapter = {
|
|
883
|
+
* async fetch(identity) {
|
|
884
|
+
* const res = await fetch(`/api/lookup/${identity.ic}`) // infinite recursion
|
|
885
|
+
* return res.json()
|
|
886
|
+
* },
|
|
887
|
+
* ...
|
|
888
|
+
* }
|
|
889
|
+
* ```
|
|
890
|
+
*
|
|
891
|
+
* @example RIGHT — explicit globalThis access:
|
|
892
|
+
* ```ts
|
|
893
|
+
* const adapter: SourceAdapter = {
|
|
894
|
+
* async fetch(identity) {
|
|
895
|
+
* const res = await globalThis.fetch(`/api/lookup/${identity.ic}`)
|
|
896
|
+
* return res.json()
|
|
897
|
+
* },
|
|
898
|
+
* ...
|
|
899
|
+
* }
|
|
900
|
+
* ```
|
|
901
|
+
*
|
|
902
|
+
* Implementation note: this method is named `fetch` which shadows
|
|
903
|
+
* the global `fetch()` inside the method body — see the @example
|
|
904
|
+
* blocks above. The fake-* reference adapters in
|
|
905
|
+
* @finsys/adapter-toolkit use the explicit-globalThis pattern.
|
|
906
|
+
* Partner repos can also pin this with the lint rule
|
|
907
|
+
* `no-restricted-syntax`: `CallExpression[callee.name='fetch']`
|
|
908
|
+
* inside any method literal named `fetch`.
|
|
909
|
+
*/
|
|
910
|
+
fetch?(identity: ApplicantIdentity<E>): Promise<RawPayload>;
|
|
806
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* Identity payload the host hands to fetch() so adapters can call
|
|
914
|
+
* partner APIs with the right per-applicant identifiers.
|
|
915
|
+
*
|
|
916
|
+
* The shape is an intersection of three pieces:
|
|
917
|
+
* - Core fields (`ihsId`, `ic`, `fullName`) — always present, the
|
|
918
|
+
* host populates them from the IHS row before invoking fetch().
|
|
919
|
+
* - Partner extensions (`E`) — typed declaratively per adapter.
|
|
920
|
+
* Defaults to `{}` (no extra typed fields) for adapters that only
|
|
921
|
+
* need the core trio.
|
|
922
|
+
* - Open index signature (`[k: string]: unknown`) — catches any
|
|
923
|
+
* ad-hoc field a host might pass through; reads land on `unknown`
|
|
924
|
+
* so partners must validate before use.
|
|
925
|
+
*
|
|
926
|
+
* Narrowing example (the ergonomics win this type is designed to deliver):
|
|
927
|
+
*
|
|
928
|
+
* interface CelcomExt { msisdn: string; accountRef: string }
|
|
929
|
+
*
|
|
930
|
+
* // declare adapter with the extension shape baked in:
|
|
931
|
+
* const celcom: SourceAdapter<CelcomExt> = {
|
|
932
|
+
* async fetch(identity) {
|
|
933
|
+
* identity.ic // string
|
|
934
|
+
* identity.msisdn // string (NOT unknown — narrowed by E)
|
|
935
|
+
* identity.foo // unknown (falls through to index signature)
|
|
936
|
+
* // ...
|
|
937
|
+
* },
|
|
938
|
+
* // ...
|
|
939
|
+
* }
|
|
940
|
+
*
|
|
941
|
+
* The intersection puts narrowed members at the root, so partners get
|
|
942
|
+
* `identity.msisdn` (not `identity.extensions?.msisdn`) — matching the
|
|
943
|
+
* pattern the host actually uses (it spreads partner-required fields
|
|
944
|
+
* onto the root identity object, no nested 'extensions' envelope).
|
|
945
|
+
*
|
|
946
|
+
* Examples:
|
|
947
|
+
* - Telco adapter declares `requiredIdentityFields: ['msisdn']`.
|
|
948
|
+
* Identity arrives with msisdn populated; adapter calls partner API.
|
|
949
|
+
* (Don't include 'ihsId', 'ic', or 'fullName' in
|
|
950
|
+
* `requiredIdentityFields` — those are core fields. `ic` in
|
|
951
|
+
* particular can be legitimately empty for non-MY scope, so
|
|
952
|
+
* declaring it required would cause the host to skip every applicant.)
|
|
953
|
+
* - Payment-network adapter declares `requiredIdentityFields:
|
|
954
|
+
* ['businessRegistrationNumber']`. Adapter looks it up + calls.
|
|
955
|
+
* - An adapter that only needs the IHS id declares no extra
|
|
956
|
+
* fields; identity has just the core trio.
|
|
957
|
+
*/
|
|
958
|
+
type ApplicantIdentity<E extends Record<string, unknown> = {}> = {
|
|
959
|
+
/** Internal IHS id — always present. */
|
|
960
|
+
readonly ihsId: number;
|
|
961
|
+
/** Malaysian IC (or analogous national id). May be empty for non-MY scope. */
|
|
962
|
+
readonly ic: string;
|
|
963
|
+
/** Applicant full legal name. */
|
|
964
|
+
readonly fullName: string;
|
|
965
|
+
} & E & {
|
|
966
|
+
/** Catch-all for ad-hoc reads (typed `unknown` — validate before use). */
|
|
967
|
+
readonly [key: string]: unknown;
|
|
968
|
+
};
|
|
807
969
|
/**
|
|
808
970
|
* Typed error thrown by adapters on expected failure paths. The
|
|
809
971
|
* `reason` discriminator lets the host app's adapter runner classify
|
|
@@ -984,6 +1146,28 @@ interface AdapterManifest {
|
|
|
984
1146
|
* host imports it dynamically.
|
|
985
1147
|
*/
|
|
986
1148
|
readonly implementation: DeclarativeImplementation | TypescriptImplementation;
|
|
1149
|
+
/**
|
|
1150
|
+
* v2.7.0 — partner-specific identity fields the adapter needs from
|
|
1151
|
+
* the IHS row when fetch() is invoked. Strings name the keys that
|
|
1152
|
+
* MUST be present on the ApplicantIdentity payload — the host
|
|
1153
|
+
* validates per-applicant before calling fetch() and skips the
|
|
1154
|
+
* adapter (with a logged warning) if any required field is missing.
|
|
1155
|
+
*
|
|
1156
|
+
* Examples:
|
|
1157
|
+
* - Telco adapter: `["ic", "msisdn"]`
|
|
1158
|
+
* - Payment-network adapter: `["businessRegistrationNumber"]`
|
|
1159
|
+
* - Bank-statement-from-OCR adapter: `[]` (no fetch — extract
|
|
1160
|
+
* reads from FinXtract output staged elsewhere)
|
|
1161
|
+
*
|
|
1162
|
+
* Omit entirely (or use empty array) for adapters that don't
|
|
1163
|
+
* implement fetch() — the host then passes an empty raw payload
|
|
1164
|
+
* to extract() and the adapter is responsible for sourcing data
|
|
1165
|
+
* via some other path (FinXtract output, webhook ingest, etc.).
|
|
1166
|
+
*
|
|
1167
|
+
* Core identity fields (`ihsId`, `ic`, `fullName`) are always
|
|
1168
|
+
* present — declaring them is harmless but unnecessary.
|
|
1169
|
+
*/
|
|
1170
|
+
readonly requiredIdentityFields?: ReadonlyArray<string>;
|
|
987
1171
|
/**
|
|
988
1172
|
* Optional free-form notes — useful for partner-side documentation
|
|
989
1173
|
* (where to find the adapter's source, who owns it, what version of
|
|
@@ -1036,4 +1220,4 @@ interface FieldMapEntry {
|
|
|
1036
1220
|
readonly transform?: "identity" | "pct_to_ratio01" | "to_boolean" | "to_integer";
|
|
1037
1221
|
}
|
|
1038
1222
|
|
|
1039
|
-
export { ALL_AGGREGATION_OPS, type AdapterCategory, AdapterError, type AdapterErrorReason, type AdapterExtraction, type AdapterManifest, type AggregationOp, BASE_FIELD_SPECS, BasicFormField, type CanonicalFieldName, type CanonicalFieldSpec, type CanonicalFieldValue, type CanonicalFieldValues, type Category, type CategorySchema, type Choice, DEFAULT_VALIDATOR_DEFINITIONS, type DeclarativeImplementation, type DocExtractionResult, DocExtractionStatus, type DropdownOption, type EditorValidator, ExtractionFileType, type ExtractionJobRecord, ExtractionJobStatus, type ExtractionStatusResult, FIELD_TYPE_DEFINITIONS, type FieldData, type FieldGroup, type FieldMapEntry, type FieldReference, FieldType, type FileFieldTableData, type FileFieldTableItem, FileFieldTableType, FileFormField, FormField, FormFieldCategory, type FormFieldInputType, type FormFieldType, type FormFieldTypeDefinitions, FormFieldValidator, FormSpec, type FormValidatorDefinitions, IHS_FAILURE_STATUSES, IHS_TERMINAL_STATUSES, IHS_VALID_STATUSES, type IhsDetailCategory, type IhsFieldDetail, IhsStatus, IhsValueFormat, type InstanceValue, type PageConfig, type RHFSchemaOutput, type RHFStep, type RawPayload, type ResolvedField, Role, type SourceAdapter, type SurveyElementJSON, type SurveyJSON, type SurveyPageJSON, type TypescriptImplementation, type UnifiedFormConfig, type Validator, allCategories, applyAggregation, applyDynamicTitles, buildFileFieldTables, categoryFieldsOf, categoryForField, categorySchemaOf, evaluateExpression, extractTimePeriods, generateRHFSchema, generateSurveyJson, getBaseCategories, getBaseFieldNames, getBaseFieldSpecMap, getBaseFieldSpecs, getCategoryName, getDisplayName, getDisplayNames, getGroupDisplayNames, getPastMonthLabel, getPastYearLabel, getStepDefaultValues, getStepSchema, groupColumnsByTimePeriod, groupDetailsByCategory, groupFieldsByCategory, groupFieldsByPattern, isFailureIhsStatus, isTerminalIhsStatus, isValidIhsStatus, processIhsDetails, resolveExtractionStatus, resolvePageFields, validateFormConfig, validateFormSpec, validatePagesConfig };
|
|
1223
|
+
export { ADAPTER_CATEGORY_IDS, ALL_AGGREGATION_OPS, type AdapterCategory, AdapterError, type AdapterErrorReason, type AdapterExtraction, type AdapterManifest, type AggregationOp, type ApplicantIdentity, BASE_FIELD_SPECS, BasicFormField, type CanonicalFieldName, type CanonicalFieldSpec, type CanonicalFieldValue, type CanonicalFieldValues, type Category, type CategorySchema, type Choice, DEFAULT_VALIDATOR_DEFINITIONS, type DeclarativeImplementation, type DocExtractionResult, DocExtractionStatus, type DropdownOption, type EditorValidator, ExtractionFileType, type ExtractionJobRecord, ExtractionJobStatus, type ExtractionStatusResult, FIELD_TYPE_DEFINITIONS, type FieldData, type FieldGroup, type FieldMapEntry, type FieldReference, FieldType, type FileFieldTableData, type FileFieldTableItem, FileFieldTableType, FileFormField, FormField, FormFieldCategory, type FormFieldInputType, type FormFieldType, type FormFieldTypeDefinitions, FormFieldValidator, FormSpec, type FormValidatorDefinitions, IHS_FAILURE_STATUSES, IHS_TERMINAL_STATUSES, IHS_VALID_STATUSES, type IhsDetailCategory, type IhsFieldDetail, IhsStatus, IhsValueFormat, type InstanceValue, type PageConfig, type RHFSchemaOutput, type RHFStep, type RawPayload, type ResolvedField, Role, type SourceAdapter, type SurveyElementJSON, type SurveyJSON, type SurveyPageJSON, type TypescriptImplementation, type UnifiedFormConfig, type Validator, allCategories, applyAggregation, applyDynamicTitles, assertAdapterCategory, buildFileFieldTables, categoryFieldsOf, categoryForField, categorySchemaOf, evaluateExpression, extractTimePeriods, generateRHFSchema, generateSurveyJson, getBaseCategories, getBaseFieldNames, getBaseFieldSpecMap, getBaseFieldSpecs, getCategoryName, getDisplayName, getDisplayNames, getGroupDisplayNames, getPastMonthLabel, getPastYearLabel, getStepDefaultValues, getStepSchema, groupColumnsByTimePeriod, groupDetailsByCategory, groupFieldsByCategory, groupFieldsByPattern, isAdapterCategory, isFailureIhsStatus, isTerminalIhsStatus, isValidIhsStatus, processIhsDetails, resolveExtractionStatus, resolvePageFields, validateFormConfig, validateFormSpec, validatePagesConfig };
|