diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..bbf6a64 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,46 @@ +name: publish + +on: [push, workflow_dispatch] + +permissions: + contents: write + id-token: write + +jobs: + jsr: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Publish package + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + npx jsr publish --allow-slow-types + else + npx jsr publish --allow-slow-types --dry-run + fi + + npm: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22.x" + registry-url: "https://registry.npmjs.org" + + - run: | + npm install + npm run build + + - env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + npm publish --provenance --access public + else + npm publish --provenance --access public --dry-run + fi diff --git a/jsr.json b/jsr.json new file mode 100644 index 0000000..b0168da --- /dev/null +++ b/jsr.json @@ -0,0 +1,11 @@ +{ + "name": "@promplate/partial-json", + "version": "0.1.7", + "exports": { + ".": "./src/index.ts", + "./options": "./src/options.ts" + }, + "publish": { + "include": ["src", "README.md", "LICENSE", "jsr.json"] + } +} diff --git a/package.json b/package.json index 65eb322..b00d046 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "partial-json", - "version": "0.1.6", + "version": "0.1.7", "description": "Parse partial JSON generated by LLM", "keywords": [ "JSON", diff --git a/src/index.ts b/src/index.ts index 3f0141c..976a72c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,11 +5,15 @@ class PartialJSON extends Error { } class MalformedJSON extends Error { } -/** Partial JSON parser +/** + * Parse incomplete JSON * @param {string} jsonString Partial JSON to be parsed - * @param {number} allowPartial Specify what kind of partialness is allowed during JSON parsing + * @param {number} allowPartial Specify what types are allowed to be partial, see {@link Allow} for details + * @returns The parsed JSON + * @throws {PartialJSON} If the JSON is incomplete (related to the `allow` parameter) + * @throws {MalformedJSON} If the JSON is malformed */ -const parseJSON = (jsonString: string, allowPartial: number = Allow.ALL) => { +function parseJSON(jsonString: string, allowPartial: number = Allow.ALL): any { if (typeof jsonString !== "string") { throw new TypeError(`expecting str, got ${typeof jsonString}`); } @@ -83,7 +87,7 @@ const _parseJSON = (jsonString: string, allow: number) => { return JSON.parse(jsonString.substring(start, index - Number(escape)) + '"'); } catch (e) { // SyntaxError: Invalid escape sequence - return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("\\", Math.max(0, index - 5))) + '"'); + return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("\\")) + '"'); } } markPartialJSON("Unterminated string literal"); diff --git a/src/options.ts b/src/options.ts index c622034..5668d51 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,11 +1,53 @@ +/** + * Sometimes you don't allow every type to be partially parsed. + * For example, you may not want a partial number because it may increase its size gradually before it's complete. + * In this case, you can use the `Allow` object to control what types you allow to be partially parsed. + * @module + */ + +/** + * allow partial strings like `"hello \u12` to be parsed as `"hello "` + */ export const STR = 0b000000001; + +/** + * allow partial numbers like `123.` to be parsed as `123` + */ export const NUM = 0b000000010; + +/** + * allow partial arrays like `[1, 2,` to be parsed as `[1, 2]` + */ export const ARR = 0b000000100; + +/** + * allow partial objects like `{"a": 1, "b":` to be parsed as `{"a": 1}` + */ export const OBJ = 0b000001000; + +/** + * allow `nu` to be parsed as `null` + */ export const NULL = 0b000010000; + +/** + * allow `tr` to be parsed as `true`, and `fa` to be parsed as `false` + */ export const BOOL = 0b000100000; + +/** + * allow `Na` to be parsed as `NaN` + */ export const NAN = 0b001000000; + +/** + * allow `Inf` to be parsed as `Infinity` + */ export const INFINITY = 0b010000000; + +/** + * allow `-Inf` to be parsed as `-Infinity` + */ export const _INFINITY = 0b100000000; export const INF = INFINITY | _INFINITY; @@ -14,6 +56,26 @@ export const ATOM = STR | NUM | SPECIAL; export const COLLECTION = ARR | OBJ; export const ALL = ATOM | COLLECTION; +/** + * Control what types you allow to be partially parsed. + * The default is to allow all types to be partially parsed, which in most casees is the best option. + * @example + * If you don't want to allow partial objects, you can use the following code: + * ```ts + * import { Allow, parse } from "partial-json"; + * parse(`[{"a": 1, "b": 2}, {"a": 3,`, Allow.ARR); // [ { a: 1, b: 2 } ] + * ``` + * Or you can use `~` to disallow a type: + * ```ts + * parse(`[{"a": 1, "b": 2}, {"a": 3,`, ~Allow.OBJ); // [ { a: 1, b: 2 } ] + * ``` + * @example + * If you don't want to allow partial strings, you can use the following code: + * ```ts + * import { Allow, parse } from "partial-json"; + * parse(`["complete string", "incompl`, ~Allow.STR); // [ 'complete string' ] + * ``` + */ export const Allow = { STR, NUM, ARR, OBJ, NULL, BOOL, NAN, INFINITY, _INFINITY, INF, SPECIAL, ATOM, COLLECTION, ALL }; export default Allow; diff --git a/tests/examples.test.js b/tests/examples.test.js index 4d62472..86c7c02 100644 --- a/tests/examples.test.js +++ b/tests/examples.test.js @@ -4,6 +4,7 @@ import { test, expect } from "vitest"; test("str", () => { expect(parse('"', STR)).toBe(""); + expect(parse('" \\x12', STR)).toBe(" "); expect(() => parse('"', ~STR)).toThrow(PartialJSON); });