@ingestron/core 0.12.1 → 0.12.3

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 CHANGED
@@ -9,8 +9,8 @@ connector runtime assets and standards through explicit package references.
9
9
 
10
10
  ## Use the library
11
11
 
12
- Requires Node 22.12 or newer for runtime operations. The npm package name is `@ingestron/core`;
13
- initial registry publication is pending. To build the public source checkout:
12
+ Requires Node 22.12 or newer for runtime operations. The npm package name is
13
+ `@ingestron/core`. To build the public source checkout:
14
14
 
15
15
  ```sh
16
16
  pnpm install --frozen-lockfile
@@ -36,6 +36,14 @@ project through a reviewed change proposal. It needs no provider, cloud account
36
36
  source data. Building native assets requires an installed provider and configured
37
37
  flows.
38
38
 
39
+ In a project, `packages` maps short names to exact installed releases. The
40
+ `packages.lock.yaml` file retains each full repository reference, commit and
41
+ file hashes. An ingestion connection holds endpoint and authentication settings;
42
+ each `flow.tables` entry selects one physical source object and its ODCS contract
43
+ selects the output columns. Connector packages define the allowed table source
44
+ shape, so the same project layout works across source types. Older stream-based
45
+ connector releases remain supported.
46
+
39
47
  ## Integration guides
40
48
 
41
49
  - [Operation and editor API](docs/api.md): requests, results, permissions, errors
@@ -640,7 +640,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
640
640
  THE SOFTWARE.
641
641
  ```
642
642
 
643
- ### yaml@2.9.0
643
+ ### yaml@2.9.1
644
644
 
645
645
  Declared licence: ISC
646
646
 
@@ -662,7 +662,7 @@ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
662
662
  THIS SOFTWARE.
663
663
  ```
664
664
 
665
- ### zod@4.5.4
665
+ ### zod@4.6.5
666
666
 
667
667
  Declared licence: MIT
668
668
 
@@ -3,15 +3,15 @@
3
3
  "dist/assets/schemas/ODCS-LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
4
4
  "dist/assets/schemas/odcs-3.1.0.json": "1a35de14c688b400fef306564f66e2af3304f057cd5cd6e7b847c1a8bf252c5b",
5
5
  "dist/assets/schemas/provenance.json": "4b5eb1fd89b81223f7946113b651d5a3a18adaa28948e43c3615dd851fb6844e",
6
- "dist/core/authoring.js": "43025703f23b61090d1ea6912e387e5ed74a93568f068339f9a7ecae88795a51",
6
+ "dist/core/authoring.js": "405c818184960c93a0cd0c041af2d415d56f62670ff91b0fb5f0d2aeed1feb9b",
7
7
  "dist/core/config.js": "b7e7138f7cc4a7b4ec8f6d0f0616c8802f2d802ec29aa09b40c1fc5c9324e016",
8
- "dist/core/connections.js": "bd351bcb05275321078409b4de83648751a51e609ab5c5fc5489f1fe31433bcd",
9
- "dist/core/connector-package.js": "c1d39fce629a524fa199ed5d0dffe6a36c500e1018fbb8379d700330372b46a7",
8
+ "dist/core/connections.js": "37b797b402788e00c51f3cac93b87342d158ed9b279753db91006d2d6c66bb4a",
9
+ "dist/core/connector-package.js": "de9729e9e4b0cf05bd1ef347e57ffd489245bb68efce5c313ccba72b78409f41",
10
10
  "dist/core/contracts.js": "a1b7fd28f776117cca86a25c6c70e852ff57ffea2bb9e7accbf2d583948d9dde",
11
11
  "dist/core/deliveries.js": "a4d2cb1213bc4c02cffb962dcb943ba98e261d37cfb21041948ec637599db080",
12
12
  "dist/core/delivery-exports.js": "d895b044c08f2059f0275724205120c72dd4bf5b74170bb01bdf42dd9d526d56",
13
13
  "dist/core/development.js": "2b55b15d545172e527cf66868d96d97c5ca081d0a83892ffd253eadaa3cc1b39",
14
- "dist/core/ecosystem-authoring.js": "782f20a08408af1c3c93ab27b521a86f2d6cddac042c2503cb58c4a76fdcd6fc",
14
+ "dist/core/ecosystem-authoring.js": "4320560eac2d2060e9e20be6a097759512c709a04fb7dc0520147ecd5ff6655e",
15
15
  "dist/core/errors.js": "8117e35fba84ae34627cb7d3bef46aaf6d8718c512140fa0ab08ccbdeeeec8c3",
16
16
  "dist/core/execution.js": "496807e7428528e31e76d51144dfd84682d435467b30a35574310af525ddeca7",
17
17
  "dist/core/extension-packs.js": "6b2aaa83e51f40e176cc4cfed574a4a17646bb3f526666fe8e0456d2912a8045",
@@ -21,16 +21,17 @@
21
21
  "dist/core/installed-plugins.js": "817ed411aef6f5b966a1828893081406805d2c8764d2ca20fd7e250e8a2168a8",
22
22
  "dist/core/model-pack-schema.js": "e3eb7ff33ddc652643e3f896637de0034004cd0cf26b0e296a074f830ae0b870",
23
23
  "dist/core/model-packs.js": "fee776ea0804dc66c9f140e57d94a3922d69c52d03c72079842ec43417616b0d",
24
- "dist/core/operations.js": "bd6217f7eb33c3c5649f8edecab3ce87d4774c9ff790feff8763ebab71866531",
24
+ "dist/core/operations.js": "7f4a9eeee19d3e1e4c1beecc0350e38d386f012edfb31c3248efcd22ebd40a77",
25
25
  "dist/core/package-references.js": "bd3085eb6dcdac399d79af109d0a9e710a0532eba61e5298dc842aec9c7ef10a",
26
- "dist/core/packages.js": "9c699b518a5c364c361d2b856f3fc6e67fe43ce0dc906b8aa474e341c5e49110",
27
- "dist/core/planner.js": "f91902f4c37a7588965360fcddbda79301b59a0b66ef2f70c0378fa5dfcc7088",
28
- "dist/core/project-build.js": "1f1002b4f6c506986c5aa6a5bb01ad5f3b4658a7a1319b65d79eaa67916fb994",
29
- "dist/core/provider-commands.js": "8bd82821f6ed0eefce8216e7c03c271f28cb9f4bfda7724d5c1de6be1855e535",
26
+ "dist/core/packages.js": "6af57d7f2b3d78837a39cf9b14a5d8151a6825dfce359680382fd8096a08d259",
27
+ "dist/core/planner.js": "974c6bac5c533a32a7c45f797ef4d857c644448aa243a929caca5917fb9f3fb8",
28
+ "dist/core/project-build.js": "18ffe962068967af255e77f1dc48fa4dd9161cd50659a8a3d9ac3b161856082e",
29
+ "dist/core/project-packages.js": "b031dd9e3c5c147e8e1ed589b5eab21b9067acbefc1d4cd1dc5b6b7018cc39cf",
30
+ "dist/core/provider-commands.js": "4f9b1565b3252a8c9c49a19a69b92fb6a72cc800cadc3e97812bf88c7e808466",
30
31
  "dist/core/provider-starter.js": "69795c1b79360e48664e91db0768f813fe4efc91aede003e1bf032f71731754c",
31
32
  "dist/core/report-pack-schema.js": "fe8ecdc2b8deacd8b2a1e46588ee06744a7b6d8e7342786e320f74cc848665fa",
32
- "dist/core/schema.js": "4cbad607f372584fe83f44a121bd978dea7529db5c1eb51168dcd62812c2eeab",
33
- "dist/core/workflows.js": "1a5fe93b34d963efb07ab1f67641106386fa3ef9d9e80bd3e59c13795925fda6",
33
+ "dist/core/schema.js": "821e79cfc56dbd8c206959c53c0cda6d81507ae78010da1ec614e204c17d96eb",
34
+ "dist/core/workflows.js": "e0b9e34fdd9b3f56403e8beb58e2b0bfbc0604beccb3113fc529c460817bc4d4",
34
35
  "dist/plugins/authoring.js": "ff985a7173c7aeda4a431a8603a4ab8631dc5134486578ede7d1301ebc3e6a19",
35
36
  "dist/plugins/compatibility.js": "a6591cfa4ca5a324fd29856e51319e39be470d6ed835ee268b293727ce11f8b7",
36
37
  "dist/plugins/generation.js": "7b0bae4de23edab4c2596d1f8ed2244d70b30fcae60395994b3964ffc845e9d5",
@@ -42,15 +43,15 @@
42
43
  "dist/version.js": "48af2aa1e425f1e387c082bbf0ad2c0c8d757c468101a614c3864153a94e6889"
43
44
  },
44
45
  "sources": {
45
- "src/core/authoring.ts": "fe80e823cfb82b39894aac9c5f733d489241b4a22bed278079254acff45a0aca",
46
+ "src/core/authoring.ts": "e1f02cf6c50cda20a62c1ea318908854939ab5be7da7de829350c7e11788be01",
46
47
  "src/core/config.ts": "f282eb246438608e1951556dfeab9372daf65330a15a9d159d36d1f940370344",
47
- "src/core/connections.ts": "19327f428382189a73a462cfd0b48943c0ba3b000633412b35da1d2ded81868b",
48
- "src/core/connector-package.ts": "f302432c3954a6230ddf02e3ae796c0a641e52984c0db3bfbfc10b3d5539e49e",
48
+ "src/core/connections.ts": "dbdaca4193efa203cffa02fd819b95f82bbd604a7678ba7dc6ba065205c49ce7",
49
+ "src/core/connector-package.ts": "a36151f6ffb49836547984eae871023ad1ad69a136f33cd6ad608496f84c85f8",
49
50
  "src/core/contracts.ts": "b7445086085b17d5ae8d5a7f4e2e849f286948e1a2a146ceb79660846e8616e5",
50
51
  "src/core/deliveries.ts": "6be0a7059c057420ba22a5515703164a892fac9e79a5bdb0e0871ea0b92ddd15",
51
52
  "src/core/delivery-exports.ts": "45a4d13e73a3809340fcd0f018dd06e2e360c67c1a117c672cd48be463c82d58",
52
53
  "src/core/development.ts": "decd47d8f6ca3289fea45331f27b99b0feb7188ae0a9e152c6459cc00658e7bd",
53
- "src/core/ecosystem-authoring.ts": "4c4385f2c9a3208d32e425183555916742a856dc6cc1bb44a806e94bb7537cba",
54
+ "src/core/ecosystem-authoring.ts": "61a44100843273e74b385704195b5e36a72afb9482f910eaacdcf3abda7be38e",
54
55
  "src/core/errors.ts": "55e7ec3239bb3bcae73cf37e700cd83b9ea3336bf5514062827a3627a606977d",
55
56
  "src/core/execution.ts": "44ebbe7b6f64e0c3c224dd6f419939e500de92b7e4e8b283b0b33266e1d1fbf1",
56
57
  "src/core/extension-packs.ts": "76671a6971598212c9ec0bb0ce9b75feee13dec09565fa0adc9f050a6ae668ec",
@@ -60,16 +61,17 @@
60
61
  "src/core/installed-plugins.ts": "951d15cdd8ca4a2c7d97fb01a46838822fe1b2fcf083ff8cf4bdca20d0997a2d",
61
62
  "src/core/model-pack-schema.ts": "bc9b30ab257fd25766cba63aa2fb6891c05e4b3eccf56d820caaab2dcc4147b4",
62
63
  "src/core/model-packs.ts": "a110d319f6feeff22a2d0d3a99ce5e49e56817d0cc27ab77d831d04a945ddfd3",
63
- "src/core/operations.ts": "3f63b1a7bab027c81a8b27dc31094910df34af6ea333b5f12590b9cf85904cb4",
64
+ "src/core/operations.ts": "e0a5a0e869e96de4e869f89e0fd794cb9fa537a16fb1f37fd8cf2b7ca777c08d",
64
65
  "src/core/package-references.ts": "5c5cee7e140158928e2e5a37f426520c7c2f042546a37eed4507d79326181f5a",
65
- "src/core/packages.ts": "b32d52adf6d4c3673cc70b5bc8638e5986fe4790b9e9ac386e057561cec13b32",
66
- "src/core/planner.ts": "498f5405f3088ee6d54ec8ab4a458813757697c150a56eab33f547aebb214da1",
67
- "src/core/project-build.ts": "feb0fe50aff7b1fbdc8d96bcdb177a72a412639d1af98c3cff3b010de90a6428",
68
- "src/core/provider-commands.ts": "f8a045230f61be0273910276b51e5c2e4bc0659f34c8a9361a38ab292c39eb4c",
66
+ "src/core/packages.ts": "5a8ac2c2c2b5ac70c97339d7d5b9d76ac44422566c1dcbb780323de512905fd4",
67
+ "src/core/planner.ts": "2c6f0d53e21cbb60fc2433e7da1245707133336274c60b9a5678614dabf09bca",
68
+ "src/core/project-build.ts": "7fa5038e7e2ff2625a30ffae282369954a5989347c6435849b72c95f2e52e247",
69
+ "src/core/project-packages.ts": "4c5cb98aeb5030db7cf2c851cd9e40ed9aae038517fba2d8d7159e884f08b7a0",
70
+ "src/core/provider-commands.ts": "195133079085aae57208bee607af439d0ea09456d526e27ec73bfdc9d727805f",
69
71
  "src/core/provider-starter.ts": "eeb504e347638bb933d204dd4d5a7e3e94ff45db136c5edd3fb457061342e405",
70
72
  "src/core/report-pack-schema.ts": "cf2c68f3725cef847627ce61acd0887f04d0067bc324841d8a552bcb12d274b0",
71
- "src/core/schema.ts": "dc1c5464878916bbdee08662320310bb202a21ebfca9e534a30746603c9aa46f",
72
- "src/core/workflows.ts": "b8afae8637eb92b58ab41a41fc245950e1c8e8820a6a27358508ae54c04c7a78",
73
+ "src/core/schema.ts": "a99a90e51be10ed56713573df6f238e6aca5b897a9679fe7bd2a8c0ad1e7f586",
74
+ "src/core/workflows.ts": "f9e8d7f0a3841b5e385a5279e202f5a3334972bb549c8c85f01e7cfbb4e16f25",
73
75
  "src/plugins/authoring.ts": "a0d68d406b4569c0b48eec458407d24c3d4eeed5b7918c6c4fcb3828ada93fe5",
74
76
  "src/plugins/compatibility.ts": "b5d357a1dc2a972cf8209311602f9fd816ba0a59cbe747f86f1b5567754073ea",
75
77
  "src/plugins/generation.ts": "a495906af09cf7bf820c7aba390bf742d7e94328ce708a2ff40f613f451fe237",
@@ -5,6 +5,8 @@ import { resolve, dirname, relative } from "node:path";
5
5
  import { parseDocument, stringify } from "yaml";
6
6
  import { check, digest, canonical, merge, Problem } from "./errors.js";
7
7
  import { Configuration } from "./config.js";
8
+ import { projectSchema } from "./schema.js";
9
+ import { projectPackages } from "./project-packages.js";
8
10
  import { contractColumns } from "./contracts.js";
9
11
  import { fence, installedProviders, canonicalPackageReference, } from "./packages.js";
10
12
  const yaml = (text, file) => {
@@ -186,7 +188,7 @@ export function addFlow(root, options) {
186
188
  const configuration = options.provider ?? resolved.defaults.provider;
187
189
  const config = resolved.providers.configurations[configuration];
188
190
  check(config, "PROVIDER", "Select a provider configuration for this flow");
189
- const pkg = resolved.providers.packages[config.package];
191
+ const pkg = projectPackages(projectSchema.parse(resolved))[config.package];
190
192
  const environments = Object.values(raw.environments).map((ref) => {
191
193
  check(ref.$resolve, "AUTHORING", "Keep editable environments in referenced files");
192
194
  const file = reader.path(ref.$resolve);
@@ -2,12 +2,14 @@ import { readFileSync } from "node:fs";
2
2
  import { fence } from "./packages.js";
3
3
  import { connectorPackageSchema } from "./connector-package.js";
4
4
  import { contractColumns } from "./contracts.js";
5
+ import { resolveModelContracts } from "./model-packs.js";
5
6
  /** Project-owned configuration; schemas and runtime assets remain plugin-owned. */
6
7
  import { Ajv2020 } from "ajv/dist/2020.js";
7
8
  import { Configuration } from "./config.js";
8
9
  import { projectSchema, flowSchema } from "./schema.js";
10
+ import { projectPackages } from "./project-packages.js";
9
11
  import { canonical, check, digest, isMap } from "./errors.js";
10
- import { canonicalPackageReference, resolvePackage, packageYaml, providerPackageSchema, } from "./packages.js";
12
+ import { configuredPackageReference, resolvePackage, packageYaml, providerPackageSchema, } from "./packages.js";
11
13
  import { checkProviderCompatibility } from "../plugins/compatibility.js";
12
14
  import { runProviderCommand, validateSchemaShape, } from "./provider-commands.js";
13
15
  function validate(schema, value, label) {
@@ -24,6 +26,7 @@ export function prepareConnection(root, environment, flowId, validateOnly = fals
24
26
  check(profile, "ENVIRONMENT", `Unknown environment ${environment}`);
25
27
  check(!profile.environment || profile.environment === environment, "ENVIRONMENT", "Environment name differs from selected profile");
26
28
  const flows = project.flows.map((f) => reader.parse(flowSchema, f));
29
+ resolveModelContracts(reader, project, flows);
27
30
  check(new Set(flows.map((f) => f.id)).size === flows.length, "FLOW", "Duplicate flow IDs");
28
31
  const flow = flows.find((f) => f.id === flowId);
29
32
  check(flow && flow.kind === "ingestion" && !flow.steps, "FLOW", "Select an ingestion flow");
@@ -35,17 +38,18 @@ export function prepareConnection(root, environment, flowId, validateOnly = fals
35
38
  check(connection, "CONNECTION", "Unknown ingestion connection");
36
39
  const configured = project.providers.configurations[flow.provider ?? project.defaults.provider ?? ""];
37
40
  check(configured, "PROVIDER", "Select the execution provider configuration");
38
- const owner = project.providers.packages[connection.package];
39
- const executor = project.providers.packages[configured.package];
41
+ const packages = projectPackages(project);
42
+ const owner = packages[connection.package];
43
+ const executor = packages[configured.package];
40
44
  check(owner && executor, "PACKAGE", "Unknown connection/execution package");
41
45
  const load = (pkg) => {
42
- const ref = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
46
+ const ref = configuredPackageReference(pkg);
43
47
  const locked = resolvePackage(root, ref);
44
48
  const manifest = providerPackageSchema.parse(packageYaml(locked.file));
45
49
  checkProviderCompatibility(manifest);
46
50
  return { ref, locked, manifest };
47
51
  };
48
- const sourceRef = canonicalPackageReference(`${owner.source}@${owner.version}`);
52
+ const sourceRef = configuredPackageReference(owner);
49
53
  const sourceLock = resolvePackage(root, sourceRef);
50
54
  const sourceRaw = packageYaml(sourceLock.file);
51
55
  check(sourceRaw.apiVersion === "ingestron.connector/v1", "CONNECTION", "Select an independently installed source connector package");
@@ -86,15 +90,22 @@ export function prepareConnection(root, environment, flowId, validateOnly = fals
86
90
  check(flow.tables && Object.keys(flow.tables).length > 0, "CONNECTION", "Select at least one table with an ODCS contract");
87
91
  const streams = new Set();
88
92
  const tables = Object.fromEntries(Object.entries(flow.tables).map(([name, table]) => {
89
- check(Object.keys(table.source).length === 1 &&
90
- typeof table.source.stream === "string", "CONNECTION", `${name}: source must identify one connector stream`);
91
- check(!streams.has(table.source.stream), "CONNECTION", "Duplicate source stream");
92
- streams.add(table.source.stream);
93
+ const source = descriptor.tableSourceSchema
94
+ ? { ...table.source, stream: name }
95
+ : table.source;
96
+ if (descriptor.tableSourceSchema)
97
+ validate(descriptor.tableSourceSchema, table.source, `${name} source`);
98
+ else
99
+ check(Object.keys(table.source).length === 1 &&
100
+ typeof table.source.stream === "string", "CONNECTION", `${name}: source must identify one connector stream`);
101
+ check(typeof source.stream === "string", "CONNECTION", `${name}: invalid source stream`);
102
+ check(!streams.has(source.stream), "CONNECTION", "Duplicate source stream");
103
+ streams.add(source.stream);
93
104
  check(!table.ingestion && !Object.keys(table.steps).length, "CONNECTION", "Connection tables do not accept ingestion/step overrides");
94
105
  return [
95
106
  name,
96
107
  {
97
- source: table.source,
108
+ source,
98
109
  contract: table.contract,
99
110
  columns: contractColumns(table.contract),
100
111
  },
@@ -29,6 +29,7 @@ export declare const connectorPackageSchema: z.ZodObject<{
29
29
  definition: z.ZodObject<{
30
30
  settingsSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
31
31
  selectionSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
32
+ tableSourceSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
32
33
  }, z.core.$strict>;
33
34
  execution: z.ZodRecord<z.ZodString, z.ZodObject<{
34
35
  modes: z.ZodArray<z.ZodString>;
@@ -31,7 +31,11 @@ export const connectorPackageSchema = z
31
31
  })
32
32
  .strict(),
33
33
  definition: z
34
- .object({ settingsSchema: schema, selectionSchema: schema })
34
+ .object({
35
+ settingsSchema: schema,
36
+ selectionSchema: schema,
37
+ tableSourceSchema: schema.optional(),
38
+ })
35
39
  .strict(),
36
40
  execution: z.record(z.string(), z
37
41
  .object({
@@ -4,6 +4,7 @@ import { Configuration } from "./config.js";
4
4
  import { preview } from "./authoring.js";
5
5
  import { check } from "./errors.js";
6
6
  import { projectSchema } from "./schema.js";
7
+ import { projectPackages } from "./project-packages.js";
7
8
  import { extensionPackSchema, packageYaml, resolvePackage, providerPackageSchema, } from "./packages.js";
8
9
  import { extensionActivities } from "./extension-packs.js";
9
10
  import { checkProviderCompatibility } from "../plugins/compatibility.js";
@@ -31,7 +32,7 @@ export function configurePack(root, args) {
31
32
  const configuration = project.providers.configurations[args.provider];
32
33
  check(configuration, "PROVIDER", "Unknown provider configuration");
33
34
  check(!project.providers.packs[args.name], "PACK", "Pack name already exists");
34
- const pkg = project.providers.packages[configuration.package];
35
+ const pkg = projectPackages(project)[configuration.package];
35
36
  const file = pkg.source.startsWith(".")
36
37
  ? reader.path(pkg.source)
37
38
  : resolvePackage(root, `${pkg.source}@${pkg.version}`).file;
@@ -261,6 +261,10 @@ export declare const operationSchemas: {
261
261
  }, z.core.$strict>;
262
262
  readonly packages_install: z.ZodObject<{
263
263
  reference: z.ZodString;
264
+ kind: z.ZodOptional<z.ZodEnum<{
265
+ connector: "connector";
266
+ provider: "provider";
267
+ }>>;
264
268
  fromGit: z.ZodOptional<z.ZodString>;
265
269
  update: z.ZodOptional<z.ZodBoolean>;
266
270
  frozen: z.ZodOptional<z.ZodBoolean>;
@@ -259,6 +259,7 @@ export const operationSchemas = {
259
259
  packages_install: z
260
260
  .object({
261
261
  reference: z.string(),
262
+ kind: z.enum(["provider", "connector"]).optional(),
262
263
  fromGit: z.string().optional(),
263
264
  update: z.boolean().optional(),
264
265
  frozen: z.boolean().optional(),
@@ -180,6 +180,10 @@ export declare function fence(root: string, name: string): string;
180
180
  export declare function packageYaml(file: string): any;
181
181
  /** Stable convenience names; locks always retain the explicit repository and path. */
182
182
  export declare function canonicalPackageReference(reference: string): string;
183
+ export declare function configuredPackageReference(pkg: {
184
+ source: string;
185
+ version: string;
186
+ }): string;
183
187
  export declare function packageLock(root: string): {
184
188
  apiVersion: "ingestron.packages-lock/v1";
185
189
  packages: Record<string, {
@@ -206,6 +210,7 @@ export declare function installPackage(root: string, reference: string, options?
206
210
  update?: boolean;
207
211
  frozen?: boolean;
208
212
  tagPrefix?: string;
213
+ kind?: "provider" | "connector";
209
214
  }): {
210
215
  repository: string;
211
216
  path: string;
@@ -276,6 +276,12 @@ export function canonicalPackageReference(reference) {
276
276
  check(repository.includes("/"), "PACKAGE", "Unknown plugin name; use an explicit owner/repository@version");
277
277
  return `${repository}/plugin/provider.yaml@${short[2]}`;
278
278
  }
279
+ export function configuredPackageReference(pkg) {
280
+ const reference = `${pkg.source}@${pkg.version}`;
281
+ return /^[a-z][a-z0-9-]*@(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(reference)
282
+ ? reference
283
+ : canonicalPackageReference(reference);
284
+ }
279
285
  const parseReference = (reference) => {
280
286
  check(!reference.endsWith("@latest"), "PACKAGE", "@latest is not supported: packages must be pinned to an exact semantic version or commit. Use plugin_versions with an explicit repository to inspect available versions.");
281
287
  const m = /^([\w.-]+\/[\w.-]+)\/(.+\.ya?ml)@(v1|(?:0|[1-9]\d*)\.\d+\.\d+|[a-f0-9]{40})$/.exec(reference);
@@ -302,6 +308,18 @@ export function packageLock(root) {
302
308
  };
303
309
  }
304
310
  export function resolvePackage(root, reference) {
311
+ if (/^[a-z][a-z0-9-]*@(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(reference)) {
312
+ const [name, version] = reference.split("@");
313
+ const matches = Object.keys(packageLock(root).packages).filter((lockedReference) => {
314
+ const installed = resolvePackage(root, lockedReference);
315
+ const manifest = packageYaml(installed.file);
316
+ return manifest.id === name && manifest.version === version;
317
+ });
318
+ check(matches.length === 1, "PACKAGE", matches.length
319
+ ? `Ambiguous installed package ${reference}; use an explicit reference`
320
+ : `Install ${reference} before building this project`);
321
+ return resolvePackage(root, matches[0]);
322
+ }
305
323
  reference = canonicalPackageReference(reference);
306
324
  const parsed = parseReference(reference), entry = packageLock(root).packages[reference];
307
325
  check(entry &&
@@ -388,13 +406,19 @@ export function installPackage(root, reference, options = {}) {
388
406
  check(!options.frozen || previous, "PACKAGE", "Frozen install requires an existing lock entry");
389
407
  if (previous &&
390
408
  !options.update &&
391
- existsSync(resolve(root, `.ingestron/packages/${digest(previous.repository)}/${previous.commit}`)))
409
+ existsSync(resolve(root, `.ingestron/packages/${digest(previous.repository)}/${previous.commit}`))) {
410
+ if (options.kind) {
411
+ const installed = resolvePackage(root, reference);
412
+ const manifest = packageYaml(installed.file);
413
+ check(manifest.apiVersion === `ingestron.${options.kind}/v1`, "PACKAGE", `Expected a ${options.kind} package at ${reference}`);
414
+ }
392
415
  return {
393
416
  reference,
394
417
  ...resolvePackage(root, reference).entry,
395
418
  cached: true,
396
419
  informationFile: savePluginInformation(root, reference),
397
420
  };
421
+ }
398
422
  const control = fence(root, ".ingestron");
399
423
  mkdirSync(control, { recursive: true });
400
424
  const guard = resolve(control, "package-install.lock");
@@ -472,6 +496,8 @@ export function installPackage(root, reference, options = {}) {
472
496
  files[m[5]] = digest(text);
473
497
  }
474
498
  const manifest = packageYaml(fence(staged, parsed.path));
499
+ if (options.kind)
500
+ check(manifest.apiVersion === `ingestron.${options.kind}/v1`, "PACKAGE", `Expected a ${options.kind} package at ${reference}`);
475
501
  if (manifest.apiVersion === "ingestron.connector/v1") {
476
502
  const connector = connectorPackageSchema.parse(manifest);
477
503
  check(connector.upstream.licenceStatus === "evidenced", "LICENCE", "Connector licence evidence is missing or conflicting; installation held");
@@ -17,6 +17,10 @@ export declare function inspectProject(root: string, environment?: string, optio
17
17
  apiVersion: "ingestron.project/v1";
18
18
  id: string;
19
19
  description?: string | undefined;
20
+ packages: Record<string, {
21
+ source: string;
22
+ version: string;
23
+ }>;
20
24
  defaults: {
21
25
  provider?: string | undefined;
22
26
  naming: {
@@ -6,6 +6,7 @@ import addFormats from "ajv-formats";
6
6
  import { resolve, relative, dirname } from "node:path";
7
7
  import { Configuration } from "./config.js";
8
8
  import { projectSchema, flowSchema, stepSchema, } from "./schema.js";
9
+ import { projectPackages } from "./project-packages.js";
9
10
  import { check, canonical, digest, isMap, merge, Problem } from "./errors.js";
10
11
  import { executeProvider } from "../plugins/provider-renderer.js";
11
12
  import { contractColumns } from "./contracts.js";
@@ -115,7 +116,7 @@ function provider(project, bindings, reader, selected) {
115
116
  check(name, "PROVIDER", "Select a provider or set defaults.provider");
116
117
  const configured = project.providers.configurations[name];
117
118
  check(configured, "PROVIDER", `Unknown provider configuration ${name}`);
118
- const pkg = project.providers.packages[configured.package];
119
+ const pkg = projectPackages(project)[configured.package];
119
120
  check(pkg, "PROVIDER", `Unknown provider package ${configured.package}`);
120
121
  let rendererCode;
121
122
  let plannerCode;
@@ -283,7 +284,7 @@ export function planProject(root, environment = "dev", options = {}) {
283
284
  const configuration = project.providers.configurations[flow.provider ?? project.defaults.provider ?? ""];
284
285
  check(configuration, "PROVIDER", "Select a provider for the ingestion flow");
285
286
  const selectedProvider = provider(project, profile.bindings, reader, flow.provider);
286
- const providerSource = project.providers.packages[configuration.package]?.source;
287
+ const providerSource = projectPackages(project)[configuration.package]?.source;
287
288
  check(selectedProvider.plannerCode, "PROVIDER", "Ingestion requires a provider planner");
288
289
  const preset = flow.ingestion.preset
289
290
  ? selectedProvider.packs[String(flow.ingestion.preset)]
@@ -4,7 +4,7 @@ import { dirname, relative, resolve } from "node:path";
4
4
  import { validateProviderFiles } from "../plugins/generation.js";
5
5
  import { inspectProject, planProject } from "./planner.js";
6
6
  import { canonical, check, digest, isMap } from "./errors.js";
7
- import { canonicalPackageReference, fence, packageYaml, providerPackageSchema, resolvePackage, } from "./packages.js";
7
+ import { configuredPackageReference, fence, packageYaml, providerPackageSchema, resolvePackage, } from "./packages.js";
8
8
  import { checkProviderCompatibility } from "../plugins/compatibility.js";
9
9
  import { prepareConnection } from "./connections.js";
10
10
  import { runProviderCommand } from "./provider-commands.js";
@@ -12,6 +12,7 @@ import { deliveryExports } from "./delivery-exports.js";
12
12
  import { renderProject, validateGeneratedFiles, writeOutput, } from "./generate.js";
13
13
  import { compilerFingerprint } from "./fingerprint.js";
14
14
  import { version } from "../version.js";
15
+ import { projectPackages } from "./project-packages.js";
15
16
  export function buildProject(root, environment, args) {
16
17
  const { project, flows, reader, profile } = inspectProject(root, environment);
17
18
  check(!(args.provider && args.flow), "SELECT", "Choose provider or flow, not both");
@@ -116,9 +117,9 @@ export function buildProject(root, environment, args) {
116
117
  const selected = flows.filter((f) => wanted.has(f.id) && owner(f) === configuration);
117
118
  const config = project.providers.configurations[configuration];
118
119
  check(config, "PROVIDER", `Unknown provider configuration ${configuration}`);
119
- const pkg = project.providers.packages[config.package];
120
+ const pkg = projectPackages(project)[config.package];
120
121
  check(pkg, "PACKAGE", `Missing execution package ${config.package}`);
121
- const reference = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
122
+ const reference = configuredPackageReference(pkg);
122
123
  const locked = pkg.source.startsWith(".")
123
124
  ? { file: reader.path(pkg.source), root, entry: undefined }
124
125
  : resolvePackage(root, reference);
@@ -0,0 +1,7 @@
1
+ import type { Project } from "./schema.js";
2
+ export declare function projectPackages(project: Project): {
3
+ [x: string]: {
4
+ source: string;
5
+ version: string;
6
+ };
7
+ };
@@ -0,0 +1,6 @@
1
+ import { check } from "./errors.js";
2
+ export function projectPackages(project) {
3
+ for (const name of Object.keys(project.packages))
4
+ check(!Object.hasOwn(project.providers.packages, name), "PACKAGE", `Package alias ${name} is declared twice`);
5
+ return { ...project.providers.packages, ...project.packages };
6
+ }
@@ -6,8 +6,9 @@ import { dirname, relative, resolve } from "node:path";
6
6
  import { Ajv2020 } from "ajv/dist/2020.js";
7
7
  import { Configuration } from "./config.js";
8
8
  import { projectSchema } from "./schema.js";
9
+ import { projectPackages } from "./project-packages.js";
9
10
  import { canonical, check, digest } from "./errors.js";
10
- import { canonicalPackageReference, fence, packageYaml, providerPackageSchema, resolvePackage, } from "./packages.js";
11
+ import { configuredPackageReference, fence, packageYaml, providerPackageSchema, resolvePackage, } from "./packages.js";
11
12
  import { executeProvider } from "../plugins/provider-renderer.js";
12
13
  import { checkProviderCompatibility } from "../plugins/compatibility.js";
13
14
  // No references, regular expressions or schema code: validation itself must be bounded.
@@ -65,9 +66,9 @@ function selected(root, environment, configuration) {
65
66
  check(project.environments[environment], "ENVIRONMENT", `Unknown environment ${environment}`);
66
67
  const configured = project.providers.configurations[configuration];
67
68
  check(configured, "PROVIDER", `Unknown provider configuration ${configuration}`);
68
- const pkg = project.providers.packages[configured.package];
69
+ const pkg = projectPackages(project)[configured.package];
69
70
  check(pkg && !pkg.source.startsWith("builtin:") && !pkg.source.startsWith("."), "PACKAGE", "Provider commands require an installed locked provider");
70
- const reference = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
71
+ const reference = configuredPackageReference(pkg);
71
72
  const source = resolvePackage(reader.root, reference);
72
73
  const manifest = providerPackageSchema.parse(packageYaml(source.file));
73
74
  checkProviderCompatibility(manifest);
@@ -209,10 +210,10 @@ export function pluginCommands(root, environment) {
209
210
  const bindings = [];
210
211
  const owners = new Map();
211
212
  for (const [configuration, binding] of Object.entries(project.providers.configurations)) {
212
- const pkg = project.providers.packages[binding.package];
213
+ const pkg = projectPackages(project)[binding.package];
213
214
  if (!pkg || pkg.source.startsWith("builtin:") || pkg.source.startsWith("."))
214
215
  continue;
215
- const reference = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
216
+ const reference = configuredPackageReference(pkg);
216
217
  const source = resolvePackage(reader.root, reference);
217
218
  const manifest = providerPackageSchema.parse(packageYaml(source.file));
218
219
  if (!manifest.commands)
@@ -19,11 +19,15 @@ export declare const projectSchema: z.ZodObject<{
19
19
  apiVersion: z.ZodLiteral<"ingestron.project/v1">;
20
20
  id: z.ZodString;
21
21
  description: z.ZodOptional<z.ZodString>;
22
+ packages: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodPreprocess<z.ZodObject<{
23
+ source: z.ZodString;
24
+ version: z.ZodString;
25
+ }, z.core.$strict>, unknown>>>;
22
26
  providers: z.ZodObject<{
23
- packages: z.ZodRecord<z.ZodString, z.ZodPreprocess<z.ZodObject<{
27
+ packages: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodPreprocess<z.ZodObject<{
24
28
  source: z.ZodString;
25
29
  version: z.ZodString;
26
- }, z.core.$strict>, unknown>>;
30
+ }, z.core.$strict>, unknown>>>;
27
31
  configurations: z.ZodRecord<z.ZodString, z.ZodObject<{
28
32
  package: z.ZodString;
29
33
  binding: z.ZodString;
@@ -30,9 +30,10 @@ export const projectSchema = z
30
30
  apiVersion: z.literal("ingestron.project/v1"),
31
31
  id,
32
32
  description: z.string().optional(),
33
+ packages: z.record(id, packageReferenceSchema).default({}),
33
34
  providers: z
34
35
  .object({
35
- packages: z.record(id, packageReferenceSchema),
36
+ packages: z.record(id, packageReferenceSchema).default({}),
36
37
  configurations: z.record(id, providerSchema),
37
38
  packs: z.record(id, packageReferenceSchema).default({}),
38
39
  })
@@ -148,6 +148,33 @@ function packageEditor(root) {
148
148
  };
149
149
  return { reader, project, finish };
150
150
  }
151
+ function packageValue(raw, name) {
152
+ return raw.packages?.[name] ?? raw.providers?.packages?.[name];
153
+ }
154
+ function packagePath(raw, name) {
155
+ return raw.packages?.[name] ||
156
+ (raw.packages && !raw.providers?.packages?.[name])
157
+ ? ["packages", name]
158
+ : ["providers", "packages", name];
159
+ }
160
+ function sameInstalledPackage(root, left, right) {
161
+ try {
162
+ const a = resolvePackage(root, left).entry;
163
+ const b = resolvePackage(root, right).entry;
164
+ return (a.repository === b.repository &&
165
+ a.path === b.path &&
166
+ a.commit === b.commit);
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ }
172
+ function preferredReference(root, raw, full, id, version) {
173
+ const short = `${id}@${version}`;
174
+ return raw.packages && sameInstalledPackage(root, short, full)
175
+ ? short
176
+ : friendlyReference(full);
177
+ }
151
178
  export function pluginConfigure(root, args) {
152
179
  const reference = canonicalPackageReference(args.reference);
153
180
  const sourceManifest = packageYaml(resolvePackage(root, reference).file);
@@ -156,41 +183,46 @@ export function pluginConfigure(root, args) {
156
183
  const { project, finish } = packageEditor(root);
157
184
  const name = identifier.parse(args.name ?? connector.id);
158
185
  const at = reference.lastIndexOf("@");
159
- const friendly = friendlyReference(reference);
186
+ const raw = project.toJS();
187
+ const friendly = preferredReference(root, raw, reference, connector.id, connector.version);
160
188
  const value = {
161
189
  source: friendly.slice(0, friendly.lastIndexOf("@")),
162
190
  version: reference.slice(at + 1),
163
191
  };
164
- const previous = project.getIn(["providers", "packages", name]);
192
+ const previous = packageValue(raw, name);
165
193
  if (previous) {
166
- const old = packageReferenceSchema.parse(project.toJS().providers.packages[name]);
167
- check(canonical(old) === canonical(value) ||
168
- (args.update && old.source === value.source), "CONFLICT", "Connector package key already exists; choose a different name");
194
+ const old = packageReferenceSchema.parse(previous);
195
+ check(sameInstalledPackage(root, `${old.source}@${old.version}`, reference) ||
196
+ (args.update &&
197
+ (old.source === value.source ||
198
+ old.source ===
199
+ friendlyReference(reference).slice(0, -value.version.length - 1))), "CONFLICT", "Connector package key already exists; choose a different name");
169
200
  }
170
- project.setIn(["providers", "packages", name], `${value.source}@${value.version}`);
201
+ project.setIn(packagePath(raw, name), `${value.source}@${value.version}`);
171
202
  return finish({ "project.yaml": project.toString() });
172
203
  }
173
204
  const { reader, project, finish } = packageEditor(root);
174
205
  const raw = project.toJS(), selected = providerReference(root, args.reference);
206
+ const preferred = preferredReference(root, raw, canonicalPackageReference(args.reference), selected.manifest.id, selected.manifest.version);
175
207
  const name = identifier.parse(args.name ?? selected.manifest.platform);
176
208
  const configurations = raw.providers.configurations;
177
209
  if (configurations[name]) {
178
- const existing = packageReferenceSchema.parse(raw.providers.packages[configurations[name].package]);
210
+ const existing = packageReferenceSchema.parse(packageValue(raw, configurations[name].package));
179
211
  check(args.update ||
180
- (friendlyReference(existing.source) === selected.source &&
181
- existing.version === selected.version), "CONFLICT", `Provider configuration ${name} already exists; use provider_migrate or a different name`);
212
+ sameInstalledPackage(root, `${existing.source}@${existing.version}`, args.reference), "CONFLICT", `Provider configuration ${name} already exists; use provider_migrate or a different name`);
182
213
  if (args.update) {
183
- check(friendlyReference(existing.source) === selected.source, "CONFLICT", "Use plugin migrate to change provider identity");
184
- project.setIn(["providers", "packages", configurations[name].package], `${selected.source}@${selected.version}`);
214
+ check(existing.source === preferred.slice(0, preferred.lastIndexOf("@")) ||
215
+ friendlyReference(existing.source) === selected.source, "CONFLICT", "Use plugin migrate to change provider identity");
216
+ project.setIn(packagePath(raw, configurations[name].package), preferred);
185
217
  return finish({ "project.yaml": project.toString() });
186
218
  }
187
- if (existing.source !== selected.source) {
188
- project.setIn(["providers", "packages", configurations[name].package], `${selected.source}@${selected.version}`);
219
+ if (!sameInstalledPackage(root, `${existing.source}@${existing.version}`, args.reference)) {
220
+ project.setIn(packagePath(raw, configurations[name].package), preferred);
189
221
  return finish({ "project.yaml": project.toString() });
190
222
  }
191
223
  return finish({});
192
224
  }
193
- check(!raw.providers.packages[name], "CONFLICT", `Package key ${name} already exists`);
225
+ check(!packageValue(raw, name), "CONFLICT", `Package key ${name} already exists`);
194
226
  const environmentNames = Object.keys(raw.environments);
195
227
  const files = providerAuthor(root, args.reference, {
196
228
  operation: "initialise",
@@ -204,7 +236,7 @@ export function pluginConfigure(root, args) {
204
236
  const skeleton = parseDocument(files["project.yaml"]).toJS();
205
237
  const defaults = skeleton.providers.configurations[skeleton.defaults.provider];
206
238
  check(defaults, "PROVIDER", "Provider did not supply its default configuration");
207
- project.setIn(["providers", "packages", name], `${selected.source}@${selected.version}`);
239
+ project.setIn(packagePath(raw, name), preferred);
208
240
  const bindingName = `${name}_${defaults.binding}`;
209
241
  project.setIn(["providers", "configurations", name], {
210
242
  ...defaults,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingestron/core",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "type": "module",
5
5
  "description": "Shared Ingestron project compiler, operations and editor schemas",
6
6
  "license": "Apache-2.0",
@@ -34,8 +34,8 @@
34
34
  "ajv": "8.20.0",
35
35
  "ajv-formats": "3.0.1",
36
36
  "quickjs-emscripten": "0.32.0",
37
- "yaml": "2.9.0",
38
- "zod": "4.5.4"
37
+ "yaml": "2.9.1",
38
+ "zod": "4.6.5"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public",
@@ -45,16 +45,6 @@
45
45
  "type": "git",
46
46
  "url": "git+https://github.com/ingestron/core.git"
47
47
  },
48
- "devDependencies": {
49
- "@types/node": "22.20.1",
50
- "prettier": "3.9.6",
51
- "tsx": "4.23.13",
52
- "typescript": "7.0.2"
53
- },
54
- "homepage": "https://ingestron.io",
55
- "bugs": {
56
- "url": "https://github.com/ingestron/core/issues"
57
- },
58
48
  "scripts": {
59
49
  "build": "node scripts/build-assets.mjs && tsc && node scripts/compiler-fingerprint.mjs",
60
50
  "test": "tsx --test --test-concurrency=1 test/unit/*.test.ts test/providers/*.test.ts test/integration/*.test.ts",
@@ -64,6 +54,19 @@
64
54
  "release:check": "node scripts/release-check.mjs",
65
55
  "package:check": "tsx scripts/package-check.ts",
66
56
  "validate": "pnpm format:check && pnpm build && pnpm test && pnpm secrets:check && pnpm release:check && pnpm package:check",
57
+ "prepack": "npm run build",
58
+ "prepublishOnly": "node scripts/release-check.mjs --publish",
67
59
  "licences:inventory": "node scripts/licence-inventory.mjs"
60
+ },
61
+ "packageManager": "pnpm@10.15.0",
62
+ "devDependencies": {
63
+ "@types/node": "22.20.1",
64
+ "prettier": "3.9.8",
65
+ "tsx": "4.23.13",
66
+ "typescript": "7.0.2"
67
+ },
68
+ "homepage": "https://ingestron.io",
69
+ "bugs": {
70
+ "url": "https://github.com/ingestron/core/issues"
68
71
  }
69
- }
72
+ }